chore: sync local changes (2026-03-12)

This commit is contained in:
2026-03-12 18:58:26 +08:00
parent 04a4cb962a
commit 939442e061
348 changed files with 91638 additions and 92091 deletions

View File

@@ -1,330 +1,330 @@
/* Epic Games 免费游戏 - 淡绿色清新风格样式 */
/* 重置样式 */
* {
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: 24px;
box-shadow: 0 8px 25px rgba(45, 80, 22, 0.08);
backdrop-filter: blur(10px);
}
.header h1 {
font-size: 2.2rem;
color: #2d5016;
margin-bottom: 10px;
font-weight: 700;
display: flex;
align-items: center;
justify-content: center;
gap: 12px;
}
.header p {
color: #5a7c65;
font-size: 1rem;
}
/* 统计信息 */
.stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
margin-bottom: 25px;
}
.stat-card {
background: rgba(255, 255, 255, 0.9);
padding: 16px;
border-radius: 15px;
text-align: center;
box-shadow: 0 4px 18px rgba(45, 80, 22, 0.08);
}
.stat-number {
font-size: 1.8rem;
font-weight: 700;
color: #1b5e20;
margin-bottom: 5px;
}
.stat-label {
color: #5a7c65;
font-size: 0.9rem;
}
/* 游戏网格 */
.games-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: 20px;
margin-bottom: 20px;
}
/* 游戏卡片 */
.game-card {
background: rgba(255, 255, 255, 0.95);
border-radius: 18px;
overflow: hidden;
box-shadow: 0 6px 25px rgba(45, 80, 22, 0.1);
transition: all 0.3s ease;
position: relative;
}
.game-card:hover {
transform: translateY(-5px);
box-shadow: 0 12px 35px rgba(45, 80, 22, 0.15);
}
.game-cover {
width: 100%;
height: 180px;
object-fit: cover;
border-radius: 0;
}
.game-info {
padding: 18px;
}
.game-title {
font-size: 1.1rem;
font-weight: 700;
color: #1b5e20;
margin-bottom: 8px;
line-height: 1.3;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.game-description {
color: #5a7c65;
font-size: 0.9rem;
margin-bottom: 12px;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
line-height: 1.4;
}
.game-meta {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
.game-price {
font-size: 1rem;
font-weight: 600;
}
.original-price {
color: #81c784;
text-decoration: line-through;
}
.free-price {
color: #2e7d32;
font-weight: 700;
}
.game-seller {
color: #5a7c65;
font-size: 0.85rem;
}
.game-dates {
background: rgba(129, 199, 132, 0.1);
padding: 10px;
border-radius: 10px;
margin-bottom: 12px;
font-size: 0.85rem;
color: #2d5016;
}
.free-period {
display: flex;
justify-content: space-between;
margin-bottom: 4px;
}
.game-actions {
display: flex;
gap: 10px;
}
.btn {
flex: 1;
background: linear-gradient(135deg, #81c784 0%, #66bb6a 100%);
color: white;
border: none;
padding: 10px 16px;
border-radius: 10px;
font-size: 0.9rem;
font-weight: 600;
cursor: pointer;
transition: all 0.25s ease;
box-shadow: 0 4px 12px rgba(129, 199, 132, 0.35);
text-decoration: none;
text-align: center;
display: inline-block;
}
.btn:hover {
transform: translateY(-2px);
box-shadow: 0 6px 18px rgba(129, 199, 132, 0.45);
}
.btn-secondary {
background: linear-gradient(135deg, #a5d6a7 0%, #81c784 100%);
}
/* 状态标签 */
.status-badge {
position: absolute;
top: 12px;
right: 12px;
padding: 6px 12px;
border-radius: 20px;
font-size: 0.8rem;
font-weight: 600;
color: white;
text-shadow: 0 1px 2px rgba(0,0,0,0.2);
}
.status-free {
background: linear-gradient(135deg, #4caf50 0%, #2e7d32 100%);
}
.status-upcoming {
background: linear-gradient(135deg, #ff9800 0%, #f57c00 100%);
}
/* 加载与错误 */
.loading, .error {
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); }
}
/* 动画 */
.fade-in {
animation: fadeIn 0.6s ease-in-out;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
/* 平板端适配 */
@media (max-width: 1024px) and (min-width: 768px) {
.container { padding: 16px; }
.header h1 { font-size: 2rem; }
.games-grid { grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); }
}
/* 手机端优化 */
@media (max-width: 767px) {
.container { padding: 12px; }
.header { padding: 18px; }
.header h1 { font-size: 1.8rem; gap: 8px; }
.stats {
display: flex;
flex-direction: row;
justify-content: space-between;
gap: 8px;
}
.stat-card {
padding: 10px 8px;
flex: 1;
min-width: 0;
}
.stat-number { font-size: 1.4rem; }
.stat-label { font-size: 0.75rem; }
.games-grid {
grid-template-columns: 1fr;
gap: 16px;
}
.game-card { margin: 0 4px; }
.game-cover { height: 160px; }
.game-info { padding: 14px; }
.game-actions {
flex-direction: column;
}
.btn {
width: 100%;
padding: 12px;
}
}
/* 小屏手机优化 */
@media (max-width: 480px) {
.stats {
grid-template-columns: 1fr;
}
.game-meta {
flex-direction: column;
align-items: flex-start;
gap: 6px;
}
}
/* 高分辨率显示器优化 */
@media (min-width: 1400px) {
.games-grid {
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
}
/* Epic Games 免费游戏 - 淡绿色清新风格样式 */
/* 重置样式 */
* {
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: 24px;
box-shadow: 0 8px 25px rgba(45, 80, 22, 0.08);
backdrop-filter: blur(10px);
}
.header h1 {
font-size: 2.2rem;
color: #2d5016;
margin-bottom: 10px;
font-weight: 700;
display: flex;
align-items: center;
justify-content: center;
gap: 12px;
}
.header p {
color: #5a7c65;
font-size: 1rem;
}
/* 统计信息 */
.stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
margin-bottom: 25px;
}
.stat-card {
background: rgba(255, 255, 255, 0.9);
padding: 16px;
border-radius: 15px;
text-align: center;
box-shadow: 0 4px 18px rgba(45, 80, 22, 0.08);
}
.stat-number {
font-size: 1.8rem;
font-weight: 700;
color: #1b5e20;
margin-bottom: 5px;
}
.stat-label {
color: #5a7c65;
font-size: 0.9rem;
}
/* 游戏网格 */
.games-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: 20px;
margin-bottom: 20px;
}
/* 游戏卡片 */
.game-card {
background: rgba(255, 255, 255, 0.95);
border-radius: 18px;
overflow: hidden;
box-shadow: 0 6px 25px rgba(45, 80, 22, 0.1);
transition: all 0.3s ease;
position: relative;
}
.game-card:hover {
transform: translateY(-5px);
box-shadow: 0 12px 35px rgba(45, 80, 22, 0.15);
}
.game-cover {
width: 100%;
height: 180px;
object-fit: cover;
border-radius: 0;
}
.game-info {
padding: 18px;
}
.game-title {
font-size: 1.1rem;
font-weight: 700;
color: #1b5e20;
margin-bottom: 8px;
line-height: 1.3;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.game-description {
color: #5a7c65;
font-size: 0.9rem;
margin-bottom: 12px;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
line-height: 1.4;
}
.game-meta {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
.game-price {
font-size: 1rem;
font-weight: 600;
}
.original-price {
color: #81c784;
text-decoration: line-through;
}
.free-price {
color: #2e7d32;
font-weight: 700;
}
.game-seller {
color: #5a7c65;
font-size: 0.85rem;
}
.game-dates {
background: rgba(129, 199, 132, 0.1);
padding: 10px;
border-radius: 10px;
margin-bottom: 12px;
font-size: 0.85rem;
color: #2d5016;
}
.free-period {
display: flex;
justify-content: space-between;
margin-bottom: 4px;
}
.game-actions {
display: flex;
gap: 10px;
}
.btn {
flex: 1;
background: linear-gradient(135deg, #81c784 0%, #66bb6a 100%);
color: white;
border: none;
padding: 10px 16px;
border-radius: 10px;
font-size: 0.9rem;
font-weight: 600;
cursor: pointer;
transition: all 0.25s ease;
box-shadow: 0 4px 12px rgba(129, 199, 132, 0.35);
text-decoration: none;
text-align: center;
display: inline-block;
}
.btn:hover {
transform: translateY(-2px);
box-shadow: 0 6px 18px rgba(129, 199, 132, 0.45);
}
.btn-secondary {
background: linear-gradient(135deg, #a5d6a7 0%, #81c784 100%);
}
/* 状态标签 */
.status-badge {
position: absolute;
top: 12px;
right: 12px;
padding: 6px 12px;
border-radius: 20px;
font-size: 0.8rem;
font-weight: 600;
color: white;
text-shadow: 0 1px 2px rgba(0,0,0,0.2);
}
.status-free {
background: linear-gradient(135deg, #4caf50 0%, #2e7d32 100%);
}
.status-upcoming {
background: linear-gradient(135deg, #ff9800 0%, #f57c00 100%);
}
/* 加载与错误 */
.loading, .error {
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); }
}
/* 动画 */
.fade-in {
animation: fadeIn 0.6s ease-in-out;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
/* 平板端适配 */
@media (max-width: 1024px) and (min-width: 768px) {
.container { padding: 16px; }
.header h1 { font-size: 2rem; }
.games-grid { grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); }
}
/* 手机端优化 */
@media (max-width: 767px) {
.container { padding: 12px; }
.header { padding: 18px; }
.header h1 { font-size: 1.8rem; gap: 8px; }
.stats {
display: flex;
flex-direction: row;
justify-content: space-between;
gap: 8px;
}
.stat-card {
padding: 10px 8px;
flex: 1;
min-width: 0;
}
.stat-number { font-size: 1.4rem; }
.stat-label { font-size: 0.75rem; }
.games-grid {
grid-template-columns: 1fr;
gap: 16px;
}
.game-card { margin: 0 4px; }
.game-cover { height: 160px; }
.game-info { padding: 14px; }
.game-actions {
flex-direction: column;
}
.btn {
width: 100%;
padding: 12px;
}
}
/* 小屏手机优化 */
@media (max-width: 480px) {
.stats {
grid-template-columns: 1fr;
}
.game-meta {
flex-direction: column;
align-items: flex-start;
gap: 6px;
}
}
/* 高分辨率显示器优化 */
@media (min-width: 1400px) {
.games-grid {
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
}
}

View File

@@ -1,63 +1,63 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>Epic Games 免费游戏 - 60s API 集合</title>
<meta name="description" content="Epic Games 免费游戏列表,数据源自 60s.viki.moe提供当前免费和即将免费的游戏信息。" />
<link rel="stylesheet" href="./css/style.css" />
</head>
<body>
<div class="container">
<header class="header">
<h1>
🎮 Epic Games 免费游戏
</h1>
</header>
<!-- 加载与错误状态 -->
<section id="loading" class="loading">
<div class="spinner"></div>
<p>正在加载游戏数据,请稍候…</p>
</section>
<section id="error" class="error" style="display: none;">
<p>获取数据失败,请稍后重试</p>
<button id="refresh-btn" class="btn" style="margin-top: 15px;">重新加载</button>
</section>
<!-- 内容区域 -->
<main id="content" style="display: none;" class="fade-in">
<!-- 统计信息 -->
<div class="stats">
<div class="stat-card">
<div class="stat-number" id="total-games">0</div>
<div class="stat-label">总游戏数</div>
</div>
<div class="stat-card">
<div class="stat-number" id="free-now">0</div>
<div class="stat-label">当前免费</div>
</div>
<div class="stat-card">
<div class="stat-number" id="upcoming">0</div>
<div class="stat-label">即将免费</div>
</div>
</div>
<!-- 游戏列表 -->
<div class="games-grid" id="games-grid">
<!-- 游戏卡片将通过 JavaScript 动态生成 -->
</div>
<!-- 刷新按钮 -->
<div style="text-align: center; margin-top: 30px;">
<button id="refresh-btn" class="btn btn-secondary">🔄 刷新数据</button>
</div>
</main>
</div>
<script src="./js/script.js"></script>
</body>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>Epic Games 免费游戏 - 60s API 集合</title>
<meta name="description" content="Epic Games 免费游戏列表,数据源自 60s.viki.moe提供当前免费和即将免费的游戏信息。" />
<link rel="stylesheet" href="./css/style.css" />
</head>
<body>
<div class="container">
<header class="header">
<h1>
🎮 Epic Games 免费游戏
</h1>
</header>
<!-- 加载与错误状态 -->
<section id="loading" class="loading">
<div class="spinner"></div>
<p>正在加载游戏数据,请稍候…</p>
</section>
<section id="error" class="error" style="display: none;">
<p>获取数据失败,请稍后重试</p>
<button id="refresh-btn" class="btn" style="margin-top: 15px;">重新加载</button>
</section>
<!-- 内容区域 -->
<main id="content" style="display: none;" class="fade-in">
<!-- 统计信息 -->
<div class="stats">
<div class="stat-card">
<div class="stat-number" id="total-games">0</div>
<div class="stat-label">总游戏数</div>
</div>
<div class="stat-card">
<div class="stat-number" id="free-now">0</div>
<div class="stat-label">当前免费</div>
</div>
<div class="stat-card">
<div class="stat-number" id="upcoming">0</div>
<div class="stat-label">即将免费</div>
</div>
</div>
<!-- 游戏列表 -->
<div class="games-grid" id="games-grid">
<!-- 游戏卡片将通过 JavaScript 动态生成 -->
</div>
<!-- 刷新按钮 -->
<div style="text-align: center; margin-top: 30px;">
<button id="refresh-btn" class="btn btn-secondary">🔄 刷新数据</button>
</div>
</main>
</div>
<script src="./js/script.js"></script>
</body>
</html>

View File

@@ -1,266 +1,266 @@
// Epic Games 免费游戏 页面脚本
(function () {
'use strict';
const API = {
endpoints: [],
currentIndex: 0,
// 初始化API接口列表
async init() {
try {
const res = await fetch('./接口集合.json');
const endpoints = await res.json();
this.endpoints = endpoints.map(endpoint => `${endpoint}/v2/epic`);
} catch (e) {
// 如果无法加载接口集合,使用默认接口
this.endpoints = ['https://60s.api.shumengya.top/v2/epic'];
}
},
// 获取当前接口URL
getCurrentUrl(encoding) {
if (this.endpoints.length === 0) return null;
const url = new URL(this.endpoints[this.currentIndex]);
if (encoding) url.searchParams.set('encoding', encoding);
return url.toString();
},
// 切换到下一个接口
switchToNext() {
this.currentIndex = (this.currentIndex + 1) % this.endpoints.length;
return this.currentIndex < this.endpoints.length;
},
// 重置到第一个接口
reset() {
this.currentIndex = 0;
}
};
// DOM 元素引用
const els = {
loading: null,
error: null,
container: null,
gamesGrid: null,
totalGames: null,
freeNow: null,
upcoming: null,
refreshBtn: null,
};
function initDom() {
els.loading = document.getElementById('loading');
els.error = document.getElementById('error');
els.container = document.getElementById('content');
els.gamesGrid = document.getElementById('games-grid');
els.totalGames = document.getElementById('total-games');
els.freeNow = document.getElementById('free-now');
els.upcoming = document.getElementById('upcoming');
els.refreshBtn = document.getElementById('refresh-btn');
}
function showLoading() {
els.loading.style.display = 'block';
els.error.style.display = 'none';
els.container.style.display = 'none';
}
function showError(msg) {
els.loading.style.display = 'none';
els.error.style.display = 'block';
els.container.style.display = 'none';
els.error.querySelector('p').textContent = msg || '获取数据失败,请稍后重试';
}
function showContent() {
els.loading.style.display = 'none';
els.error.style.display = 'none';
els.container.style.display = 'block';
}
function safeText(text) {
const div = document.createElement('div');
div.textContent = text == null ? '' : String(text);
return div.innerHTML;
}
function formatDate(dateStr) {
if (!dateStr) return '';
try {
const date = new Date(dateStr);
return date.toLocaleDateString('zh-CN', {
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit'
});
} catch (e) {
return dateStr;
}
}
async function fetchData(preferLocal = false) {
if (preferLocal) {
try {
const res = await fetch('./返回接口.json', { cache: 'no-store' });
const json = await res.json();
return json;
} catch (e) {
throw new Error('本地数据加载失败');
}
}
// 重置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 res = await fetch(url, {
cache: 'no-store',
timeout: 10000 // 10秒超时
});
if (!res.ok) {
throw new Error(`HTTP ${res.status}: ${res.statusText}`);
}
const json = await res.json();
if (json && json.code === 200) {
console.log(`接口 ${i + 1} 请求成功`);
return json;
}
throw new Error(json && json.message ? json.message : '接口返回异常');
} catch (e) {
console.warn(`接口 ${i + 1} 失败:`, e.message);
// 如果不是最后一个接口,切换到下一个
if (i < API.endpoints.length - 1) {
API.switchToNext();
continue;
}
// 所有接口都失败了,尝试本地数据
console.warn('所有远程接口都失败,尝试本地数据');
try {
const res = await fetch('./返回接口.json', { cache: 'no-store' });
const json = await res.json();
return json;
} catch (e2) {
throw new Error('所有接口和本地数据都无法访问');
}
}
}
}
function createGameCard(game) {
const isFree = game.is_free_now;
const statusClass = isFree ? 'status-free' : 'status-upcoming';
const statusText = isFree ? '限时免费' : '即将免费';
return `
<div class="game-card fade-in">
<div class="status-badge ${statusClass}">${statusText}</div>
<img class="game-cover" src="${safeText(game.cover)}" alt="${safeText(game.title)} 封面" loading="lazy" />
<div class="game-info">
<h3 class="game-title">${safeText(game.title)}</h3>
<p class="game-description">${safeText(game.description)}</p>
<div class="game-meta">
<div class="game-price">
<span class="original-price">${safeText(game.original_price_desc)}</span>
<span class="free-price">免费</span>
</div>
<div class="game-seller">${safeText(game.seller)}</div>
</div>
<div class="game-dates">
<div class="free-period">
<span>开始:${formatDate(game.free_start)}</span>
<span>结束:${formatDate(game.free_end)}</span>
</div>
</div>
<div class="game-actions">
<a href="${safeText(game.link)}" target="_blank" class="btn">
${isFree ? '立即领取' : '查看详情'}
</a>
</div>
</div>
</div>
`;
}
function updateStats(games) {
const total = games.length;
const freeNow = games.filter(game => game.is_free_now).length;
const upcoming = total - freeNow;
els.totalGames.textContent = total;
els.freeNow.textContent = freeNow;
els.upcoming.textContent = upcoming;
}
function renderGames(games) {
if (!Array.isArray(games) || games.length === 0) {
els.gamesGrid.innerHTML = '<div style="grid-column: 1/-1; text-align: center; padding: 40px; color: #5a7c65;">暂无游戏数据</div>';
return;
}
// 按状态排序:免费的在前
const sortedGames = [...games].sort((a, b) => {
if (a.is_free_now && !b.is_free_now) return -1;
if (!a.is_free_now && b.is_free_now) return 1;
return 0;
});
const html = sortedGames.map(game => createGameCard(game)).join('');
els.gamesGrid.innerHTML = html;
updateStats(games);
}
function render(data) {
const games = data?.data || [];
renderGames(games);
showContent();
}
async function load() {
showLoading();
// 初始化API接口列表
await API.init();
try {
const data = await fetchData(false);
render(data);
} catch (e) {
console.error('数据获取失败:', e);
showError(e.message || '获取数据失败,请稍后重试');
}
}
function bindEvents() {
if (els.refreshBtn) {
els.refreshBtn.addEventListener('click', load);
}
// 快捷键 Ctrl+R 刷新(不拦截浏览器默认刷新)
document.addEventListener('keydown', (e) => {
if (e.ctrlKey && e.key === 'r' && !e.defaultPrevented) {
// 不阻止默认行为,让浏览器正常刷新
}
});
}
document.addEventListener('DOMContentLoaded', () => {
initDom();
bindEvents();
load();
});
// Epic Games 免费游戏 页面脚本
(function () {
'use strict';
const API = {
endpoints: [],
currentIndex: 0,
// 初始化API接口列表
async init() {
try {
const res = await fetch('./接口集合.json');
const endpoints = await res.json();
this.endpoints = endpoints.map(endpoint => `${endpoint}/v2/epic`);
} catch (e) {
// 如果无法加载接口集合,使用默认接口
this.endpoints = ['https://60s.api.shumengya.top/v2/epic'];
}
},
// 获取当前接口URL
getCurrentUrl(encoding) {
if (this.endpoints.length === 0) return null;
const url = new URL(this.endpoints[this.currentIndex]);
if (encoding) url.searchParams.set('encoding', encoding);
return url.toString();
},
// 切换到下一个接口
switchToNext() {
this.currentIndex = (this.currentIndex + 1) % this.endpoints.length;
return this.currentIndex < this.endpoints.length;
},
// 重置到第一个接口
reset() {
this.currentIndex = 0;
}
};
// DOM 元素引用
const els = {
loading: null,
error: null,
container: null,
gamesGrid: null,
totalGames: null,
freeNow: null,
upcoming: null,
refreshBtn: null,
};
function initDom() {
els.loading = document.getElementById('loading');
els.error = document.getElementById('error');
els.container = document.getElementById('content');
els.gamesGrid = document.getElementById('games-grid');
els.totalGames = document.getElementById('total-games');
els.freeNow = document.getElementById('free-now');
els.upcoming = document.getElementById('upcoming');
els.refreshBtn = document.getElementById('refresh-btn');
}
function showLoading() {
els.loading.style.display = 'block';
els.error.style.display = 'none';
els.container.style.display = 'none';
}
function showError(msg) {
els.loading.style.display = 'none';
els.error.style.display = 'block';
els.container.style.display = 'none';
els.error.querySelector('p').textContent = msg || '获取数据失败,请稍后重试';
}
function showContent() {
els.loading.style.display = 'none';
els.error.style.display = 'none';
els.container.style.display = 'block';
}
function safeText(text) {
const div = document.createElement('div');
div.textContent = text == null ? '' : String(text);
return div.innerHTML;
}
function formatDate(dateStr) {
if (!dateStr) return '';
try {
const date = new Date(dateStr);
return date.toLocaleDateString('zh-CN', {
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit'
});
} catch (e) {
return dateStr;
}
}
async function fetchData(preferLocal = false) {
if (preferLocal) {
try {
const res = await fetch('./返回接口.json', { cache: 'no-store' });
const json = await res.json();
return json;
} catch (e) {
throw new Error('本地数据加载失败');
}
}
// 重置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 res = await fetch(url, {
cache: 'no-store',
timeout: 10000 // 10秒超时
});
if (!res.ok) {
throw new Error(`HTTP ${res.status}: ${res.statusText}`);
}
const json = await res.json();
if (json && json.code === 200) {
console.log(`接口 ${i + 1} 请求成功`);
return json;
}
throw new Error(json && json.message ? json.message : '接口返回异常');
} catch (e) {
console.warn(`接口 ${i + 1} 失败:`, e.message);
// 如果不是最后一个接口,切换到下一个
if (i < API.endpoints.length - 1) {
API.switchToNext();
continue;
}
// 所有接口都失败了,尝试本地数据
console.warn('所有远程接口都失败,尝试本地数据');
try {
const res = await fetch('./返回接口.json', { cache: 'no-store' });
const json = await res.json();
return json;
} catch (e2) {
throw new Error('所有接口和本地数据都无法访问');
}
}
}
}
function createGameCard(game) {
const isFree = game.is_free_now;
const statusClass = isFree ? 'status-free' : 'status-upcoming';
const statusText = isFree ? '限时免费' : '即将免费';
return `
<div class="game-card fade-in">
<div class="status-badge ${statusClass}">${statusText}</div>
<img class="game-cover" src="${safeText(game.cover)}" alt="${safeText(game.title)} 封面" loading="lazy" />
<div class="game-info">
<h3 class="game-title">${safeText(game.title)}</h3>
<p class="game-description">${safeText(game.description)}</p>
<div class="game-meta">
<div class="game-price">
<span class="original-price">${safeText(game.original_price_desc)}</span>
<span class="free-price">免费</span>
</div>
<div class="game-seller">${safeText(game.seller)}</div>
</div>
<div class="game-dates">
<div class="free-period">
<span>开始:${formatDate(game.free_start)}</span>
<span>结束:${formatDate(game.free_end)}</span>
</div>
</div>
<div class="game-actions">
<a href="${safeText(game.link)}" target="_blank" class="btn">
${isFree ? '立即领取' : '查看详情'}
</a>
</div>
</div>
</div>
`;
}
function updateStats(games) {
const total = games.length;
const freeNow = games.filter(game => game.is_free_now).length;
const upcoming = total - freeNow;
els.totalGames.textContent = total;
els.freeNow.textContent = freeNow;
els.upcoming.textContent = upcoming;
}
function renderGames(games) {
if (!Array.isArray(games) || games.length === 0) {
els.gamesGrid.innerHTML = '<div style="grid-column: 1/-1; text-align: center; padding: 40px; color: #5a7c65;">暂无游戏数据</div>';
return;
}
// 按状态排序:免费的在前
const sortedGames = [...games].sort((a, b) => {
if (a.is_free_now && !b.is_free_now) return -1;
if (!a.is_free_now && b.is_free_now) return 1;
return 0;
});
const html = sortedGames.map(game => createGameCard(game)).join('');
els.gamesGrid.innerHTML = html;
updateStats(games);
}
function render(data) {
const games = data?.data || [];
renderGames(games);
showContent();
}
async function load() {
showLoading();
// 初始化API接口列表
await API.init();
try {
const data = await fetchData(false);
render(data);
} catch (e) {
console.error('数据获取失败:', e);
showError(e.message || '获取数据失败,请稍后重试');
}
}
function bindEvents() {
if (els.refreshBtn) {
els.refreshBtn.addEventListener('click', load);
}
// 快捷键 Ctrl+R 刷新(不拦截浏览器默认刷新)
document.addEventListener('keydown', (e) => {
if (e.ctrlKey && e.key === 'r' && !e.defaultPrevented) {
// 不阻止默认行为,让浏览器正常刷新
}
});
}
document.addEventListener('DOMContentLoaded', () => {
initDom();
bindEvents();
load();
});
})();

View File

@@ -1,3 +1,3 @@
[
"https://60s.api.shumengya.top"
]
[
"https://60s.api.shumengya.top"
]

View File

@@ -1,66 +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"
}
]
{
"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"
}
]
}

View File

@@ -1,233 +1,233 @@
/* 动态背景样式 */
body::before {
content: '';
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(135deg, #f0f8e8 0%, #e8f5e8 50%, #d4f4dd 100%);
z-index: -2;
}
body::after {
content: '';
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background:
radial-gradient(circle at 20% 80%, rgba(144, 238, 144, 0.2) 0%, transparent 50%),
radial-gradient(circle at 80% 20%, rgba(173, 255, 173, 0.2) 0%, transparent 50%),
radial-gradient(circle at 40% 40%, rgba(152, 251, 152, 0.2) 0%, transparent 50%);
z-index: -1;
animation: backgroundMove 20s ease-in-out infinite;
}
@keyframes backgroundMove {
0%, 100% {
background:
radial-gradient(circle at 20% 80%, rgba(144, 238, 144, 0.2) 0%, transparent 50%),
radial-gradient(circle at 80% 20%, rgba(173, 255, 173, 0.2) 0%, transparent 50%),
radial-gradient(circle at 40% 40%, rgba(152, 251, 152, 0.2) 0%, transparent 50%);
}
25% {
background:
radial-gradient(circle at 60% 30%, rgba(144, 238, 144, 0.2) 0%, transparent 50%),
radial-gradient(circle at 30% 70%, rgba(173, 255, 173, 0.2) 0%, transparent 50%),
radial-gradient(circle at 80% 80%, rgba(152, 251, 152, 0.2) 0%, transparent 50%);
}
50% {
background:
radial-gradient(circle at 80% 60%, rgba(144, 238, 144, 0.2) 0%, transparent 50%),
radial-gradient(circle at 20% 30%, rgba(173, 255, 173, 0.2) 0%, transparent 50%),
radial-gradient(circle at 60% 70%, rgba(152, 251, 152, 0.2) 0%, transparent 50%);
}
75% {
background:
radial-gradient(circle at 40% 90%, rgba(144, 238, 144, 0.2) 0%, transparent 50%),
radial-gradient(circle at 70% 10%, rgba(173, 255, 173, 0.2) 0%, transparent 50%),
radial-gradient(circle at 20% 60%, rgba(152, 251, 152, 0.2) 0%, transparent 50%);
}
}
/* 浮动粒子效果 */
.particles {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: -1;
}
.particle {
position: absolute;
width: 4px;
height: 4px;
background: rgba(255, 255, 255, 0.5);
border-radius: 50%;
animation: float 15s infinite linear;
}
.particle:nth-child(1) {
left: 10%;
animation-delay: 0s;
animation-duration: 12s;
}
.particle:nth-child(2) {
left: 20%;
animation-delay: 2s;
animation-duration: 18s;
}
.particle:nth-child(3) {
left: 30%;
animation-delay: 4s;
animation-duration: 15s;
}
.particle:nth-child(4) {
left: 40%;
animation-delay: 6s;
animation-duration: 20s;
}
.particle:nth-child(5) {
left: 50%;
animation-delay: 8s;
animation-duration: 14s;
}
.particle:nth-child(6) {
left: 60%;
animation-delay: 10s;
animation-duration: 16s;
}
.particle:nth-child(7) {
left: 70%;
animation-delay: 12s;
animation-duration: 22s;
}
.particle:nth-child(8) {
left: 80%;
animation-delay: 14s;
animation-duration: 13s;
}
.particle:nth-child(9) {
left: 90%;
animation-delay: 16s;
animation-duration: 19s;
}
.particle:nth-child(10) {
left: 15%;
animation-delay: 18s;
animation-duration: 17s;
}
@keyframes float {
0% {
transform: translateY(100vh) rotate(0deg);
opacity: 0;
}
10% {
opacity: 1;
}
90% {
opacity: 1;
}
100% {
transform: translateY(-100px) rotate(360deg);
opacity: 0;
}
}
/* 网格背景效果 */
.grid-background {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image:
linear-gradient(rgba(255, 255, 255, 0.1) 1px, transparent 1px),
linear-gradient(90deg, rgba(255, 255, 255, 0.1) 1px, transparent 1px);
background-size: 50px 50px;
z-index: -1;
opacity: 0.3;
animation: gridMove 30s linear infinite;
}
@keyframes gridMove {
0% {
transform: translate(0, 0);
}
100% {
transform: translate(50px, 50px);
}
}
/* 光晕效果 */
.glow-effect {
position: fixed;
top: 50%;
left: 50%;
width: 300px;
height: 300px;
background: radial-gradient(circle, rgba(74, 144, 226, 0.2) 0%, transparent 70%);
border-radius: 50%;
transform: translate(-50%, -50%);
z-index: -1;
animation: pulse 4s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% {
transform: translate(-50%, -50%) scale(1);
opacity: 0.5;
}
50% {
transform: translate(-50%, -50%) scale(1.2);
opacity: 0.8;
}
}
/* 响应式背景调整 */
@media (max-width: 768px) {
.grid-background {
background-size: 30px 30px;
}
.glow-effect {
width: 200px;
height: 200px;
}
.particle {
width: 3px;
height: 3px;
}
}
@media (max-width: 480px) {
.grid-background {
background-size: 20px 20px;
}
.glow-effect {
width: 150px;
height: 150px;
}
.particle {
width: 2px;
height: 2px;
}
/* 动态背景样式 */
body::before {
content: '';
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(135deg, #f0f8e8 0%, #e8f5e8 50%, #d4f4dd 100%);
z-index: -2;
}
body::after {
content: '';
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background:
radial-gradient(circle at 20% 80%, rgba(144, 238, 144, 0.2) 0%, transparent 50%),
radial-gradient(circle at 80% 20%, rgba(173, 255, 173, 0.2) 0%, transparent 50%),
radial-gradient(circle at 40% 40%, rgba(152, 251, 152, 0.2) 0%, transparent 50%);
z-index: -1;
animation: backgroundMove 20s ease-in-out infinite;
}
@keyframes backgroundMove {
0%, 100% {
background:
radial-gradient(circle at 20% 80%, rgba(144, 238, 144, 0.2) 0%, transparent 50%),
radial-gradient(circle at 80% 20%, rgba(173, 255, 173, 0.2) 0%, transparent 50%),
radial-gradient(circle at 40% 40%, rgba(152, 251, 152, 0.2) 0%, transparent 50%);
}
25% {
background:
radial-gradient(circle at 60% 30%, rgba(144, 238, 144, 0.2) 0%, transparent 50%),
radial-gradient(circle at 30% 70%, rgba(173, 255, 173, 0.2) 0%, transparent 50%),
radial-gradient(circle at 80% 80%, rgba(152, 251, 152, 0.2) 0%, transparent 50%);
}
50% {
background:
radial-gradient(circle at 80% 60%, rgba(144, 238, 144, 0.2) 0%, transparent 50%),
radial-gradient(circle at 20% 30%, rgba(173, 255, 173, 0.2) 0%, transparent 50%),
radial-gradient(circle at 60% 70%, rgba(152, 251, 152, 0.2) 0%, transparent 50%);
}
75% {
background:
radial-gradient(circle at 40% 90%, rgba(144, 238, 144, 0.2) 0%, transparent 50%),
radial-gradient(circle at 70% 10%, rgba(173, 255, 173, 0.2) 0%, transparent 50%),
radial-gradient(circle at 20% 60%, rgba(152, 251, 152, 0.2) 0%, transparent 50%);
}
}
/* 浮动粒子效果 */
.particles {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: -1;
}
.particle {
position: absolute;
width: 4px;
height: 4px;
background: rgba(255, 255, 255, 0.5);
border-radius: 50%;
animation: float 15s infinite linear;
}
.particle:nth-child(1) {
left: 10%;
animation-delay: 0s;
animation-duration: 12s;
}
.particle:nth-child(2) {
left: 20%;
animation-delay: 2s;
animation-duration: 18s;
}
.particle:nth-child(3) {
left: 30%;
animation-delay: 4s;
animation-duration: 15s;
}
.particle:nth-child(4) {
left: 40%;
animation-delay: 6s;
animation-duration: 20s;
}
.particle:nth-child(5) {
left: 50%;
animation-delay: 8s;
animation-duration: 14s;
}
.particle:nth-child(6) {
left: 60%;
animation-delay: 10s;
animation-duration: 16s;
}
.particle:nth-child(7) {
left: 70%;
animation-delay: 12s;
animation-duration: 22s;
}
.particle:nth-child(8) {
left: 80%;
animation-delay: 14s;
animation-duration: 13s;
}
.particle:nth-child(9) {
left: 90%;
animation-delay: 16s;
animation-duration: 19s;
}
.particle:nth-child(10) {
left: 15%;
animation-delay: 18s;
animation-duration: 17s;
}
@keyframes float {
0% {
transform: translateY(100vh) rotate(0deg);
opacity: 0;
}
10% {
opacity: 1;
}
90% {
opacity: 1;
}
100% {
transform: translateY(-100px) rotate(360deg);
opacity: 0;
}
}
/* 网格背景效果 */
.grid-background {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image:
linear-gradient(rgba(255, 255, 255, 0.1) 1px, transparent 1px),
linear-gradient(90deg, rgba(255, 255, 255, 0.1) 1px, transparent 1px);
background-size: 50px 50px;
z-index: -1;
opacity: 0.3;
animation: gridMove 30s linear infinite;
}
@keyframes gridMove {
0% {
transform: translate(0, 0);
}
100% {
transform: translate(50px, 50px);
}
}
/* 光晕效果 */
.glow-effect {
position: fixed;
top: 50%;
left: 50%;
width: 300px;
height: 300px;
background: radial-gradient(circle, rgba(74, 144, 226, 0.2) 0%, transparent 70%);
border-radius: 50%;
transform: translate(-50%, -50%);
z-index: -1;
animation: pulse 4s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% {
transform: translate(-50%, -50%) scale(1);
opacity: 0.5;
}
50% {
transform: translate(-50%, -50%) scale(1.2);
opacity: 0.8;
}
}
/* 响应式背景调整 */
@media (max-width: 768px) {
.grid-background {
background-size: 30px 30px;
}
.glow-effect {
width: 200px;
height: 200px;
}
.particle {
width: 3px;
height: 3px;
}
}
@media (max-width: 480px) {
.grid-background {
background-size: 20px 20px;
}
.glow-effect {
width: 150px;
height: 150px;
}
.particle {
width: 2px;
height: 2px;
}
}

View File

@@ -1,461 +1,461 @@
/* 全局样式重置 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
line-height: 1.6;
color: #333;
min-height: 100vh;
overflow-x: hidden;
/* 隐藏滚动条但保留滚动功能 */
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IE and Edge */
}
/* 隐藏 Webkit 浏览器的滚动条 */
body::-webkit-scrollbar,
html::-webkit-scrollbar,
*::-webkit-scrollbar {
display: none;
}
/* 全局隐藏滚动条但保留滚动功能 */
html {
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IE and Edge */
}
/* 容器样式 */
.container {
min-height: 100vh;
display: flex;
flex-direction: column;
position: relative;
z-index: 1;
}
/* 头部样式 */
.header {
text-align: center;
padding: 3rem 2rem 2rem;
background: linear-gradient(135deg, rgba(144, 238, 144, 0.15), rgba(152, 251, 152, 0.15));
backdrop-filter: blur(10px);
border-bottom: 1px solid rgba(255, 255, 255, 0.2);
}
.header h1 {
font-size: 2.5rem;
font-weight: 700;
background: linear-gradient(135deg, #228B22, #32CD32);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
margin-bottom: 0.5rem;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.header h1 i {
margin-right: 0.5rem;
background: linear-gradient(135deg, #228B22, #32CD32);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.subtitle {
font-size: 1.1rem;
color: #666;
font-weight: 300;
}
/* 主要内容区域 */
.main-content {
flex: 1;
padding: 2rem;
max-width: 800px;
margin: 0 auto;
width: 100%;
}
/* 查询按钮区域 */
.query-section {
text-align: center;
margin-bottom: 2rem;
}
.query-btn {
background: linear-gradient(135deg, #228B22, #32CD32);
color: white;
border: none;
padding: 1rem 2rem;
font-size: 1.1rem;
font-weight: 600;
border-radius: 50px;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 4px 15px rgba(34, 139, 34, 0.3);
display: inline-flex;
align-items: center;
gap: 0.5rem;
min-width: 200px;
justify-content: center;
}
.query-btn:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(34, 139, 34, 0.4);
background: linear-gradient(135deg, #1e7e1e, #2eb82e);
}
.query-btn:active {
transform: translateY(0);
}
.query-btn:disabled {
opacity: 0.6;
cursor: not-allowed;
transform: none;
}
/* 加载动画 */
.loading {
text-align: center;
padding: 2rem;
}
.spinner {
width: 50px;
height: 50px;
border: 4px solid #f3f3f3;
border-top: 4px solid #4a90e2;
border-radius: 50%;
animation: spin 1s linear infinite;
margin: 0 auto 1rem;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.loading p {
color: #666;
font-size: 1rem;
}
/* IP信息卡片 */
.ip-info {
animation: fadeInUp 0.6s ease;
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.ip-card {
background: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(10px);
border-radius: 20px;
padding: 2rem;
margin-bottom: 2rem;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
}
.ip-header {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 1.5rem;
padding-bottom: 1rem;
border-bottom: 2px solid #f0f0f0;
}
.ip-header i {
font-size: 1.5rem;
color: #4a90e2;
}
.ip-header h2 {
font-size: 1.5rem;
font-weight: 600;
color: #333;
}
.ip-display {
display: flex;
align-items: center;
justify-content: center;
gap: 1rem;
margin-bottom: 1.5rem;
padding: 1.5rem;
background: linear-gradient(135deg, rgba(74, 144, 226, 0.1), rgba(80, 200, 120, 0.1));
border-radius: 15px;
border: 2px solid rgba(74, 144, 226, 0.2);
}
.ip-address {
font-size: 2rem;
font-weight: 700;
font-family: 'Courier New', monospace;
color: #2c3e50;
background: linear-gradient(135deg, #4a90e2, #50c878);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.copy-btn {
background: #4a90e2;
color: white;
border: none;
padding: 0.5rem;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s ease;
font-size: 1rem;
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
}
.copy-btn:hover {
background: #3a7bc8;
transform: scale(1.1);
}
.ip-details {
display: grid;
gap: 1rem;
}
.detail-item {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.75rem;
background: rgba(248, 249, 250, 0.8);
border-radius: 10px;
transition: all 0.3s ease;
}
.detail-item:hover {
background: rgba(74, 144, 226, 0.1);
transform: translateX(5px);
}
.detail-item i {
color: #4a90e2;
width: 20px;
text-align: center;
}
.detail-item .label {
font-weight: 600;
color: #555;
min-width: 80px;
}
.detail-item .value {
color: #333;
font-weight: 500;
}
/* IP地址说明区域 */
.ip-explanation {
background: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(10px);
border-radius: 20px;
padding: 2rem;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
}
.ip-explanation h3 {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 1rem;
font-size: 1.3rem;
color: #333;
}
.ip-explanation h3 i {
color: #4a90e2;
}
.ip-explanation p {
color: #666;
line-height: 1.8;
margin-bottom: 1.5rem;
}
.features {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 1rem;
}
.feature-item {
display: flex;
align-items: flex-start;
gap: 1rem;
padding: 1rem;
background: rgba(248, 249, 250, 0.8);
border-radius: 12px;
transition: all 0.3s ease;
}
.feature-item:hover {
background: rgba(74, 144, 226, 0.1);
transform: translateY(-2px);
}
.feature-item i {
color: #4a90e2;
font-size: 1.5rem;
margin-top: 0.2rem;
}
.feature-item h4 {
font-size: 1rem;
font-weight: 600;
color: #333;
margin-bottom: 0.3rem;
}
.feature-item p {
font-size: 0.9rem;
color: #666;
line-height: 1.5;
margin: 0;
}
/* 错误信息 */
.error-message {
text-align: center;
padding: 2rem;
background: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(10px);
border-radius: 20px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
border: 1px solid rgba(255, 99, 99, 0.3);
animation: fadeInUp 0.6s ease;
}
.error-message i {
font-size: 3rem;
color: #ff6b6b;
margin-bottom: 1rem;
}
.error-message p {
color: #666;
font-size: 1.1rem;
margin-bottom: 1.5rem;
}
.retry-btn {
background: #ff6b6b;
color: white;
border: none;
padding: 0.75rem 1.5rem;
font-size: 1rem;
font-weight: 600;
border-radius: 25px;
cursor: pointer;
transition: all 0.3s ease;
}
.retry-btn:hover {
background: #ff5252;
transform: translateY(-2px);
box-shadow: 0 4px 15px rgba(255, 107, 107, 0.3);
}
/* 页脚 */
.footer {
text-align: center;
padding: 2rem;
background: rgba(248, 249, 250, 0.8);
backdrop-filter: blur(10px);
border-top: 1px solid rgba(255, 255, 255, 0.2);
color: #666;
font-size: 0.9rem;
}
/* 响应式设计 */
@media (max-width: 768px) {
.header {
padding: 2rem 1rem 1.5rem;
}
.header h1 {
font-size: 2rem;
}
.main-content {
padding: 1rem;
}
.ip-card, .ip-explanation {
padding: 1.5rem;
}
.ip-address {
font-size: 1.5rem;
}
.ip-display {
flex-direction: column;
gap: 1rem;
}
.features {
grid-template-columns: 1fr;
}
.detail-item {
flex-direction: column;
align-items: flex-start;
gap: 0.3rem;
}
.detail-item .label {
min-width: auto;
}
}
@media (max-width: 480px) {
.header h1 {
font-size: 1.8rem;
}
.query-btn {
padding: 0.875rem 1.5rem;
font-size: 1rem;
min-width: 180px;
}
.ip-address {
font-size: 1.3rem;
}
.ip-card, .ip-explanation {
padding: 1rem;
}
/* 全局样式重置 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
line-height: 1.6;
color: #333;
min-height: 100vh;
overflow-x: hidden;
/* 隐藏滚动条但保留滚动功能 */
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IE and Edge */
}
/* 隐藏 Webkit 浏览器的滚动条 */
body::-webkit-scrollbar,
html::-webkit-scrollbar,
*::-webkit-scrollbar {
display: none;
}
/* 全局隐藏滚动条但保留滚动功能 */
html {
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IE and Edge */
}
/* 容器样式 */
.container {
min-height: 100vh;
display: flex;
flex-direction: column;
position: relative;
z-index: 1;
}
/* 头部样式 */
.header {
text-align: center;
padding: 3rem 2rem 2rem;
background: linear-gradient(135deg, rgba(144, 238, 144, 0.15), rgba(152, 251, 152, 0.15));
backdrop-filter: blur(10px);
border-bottom: 1px solid rgba(255, 255, 255, 0.2);
}
.header h1 {
font-size: 2.5rem;
font-weight: 700;
background: linear-gradient(135deg, #228B22, #32CD32);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
margin-bottom: 0.5rem;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.header h1 i {
margin-right: 0.5rem;
background: linear-gradient(135deg, #228B22, #32CD32);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.subtitle {
font-size: 1.1rem;
color: #666;
font-weight: 300;
}
/* 主要内容区域 */
.main-content {
flex: 1;
padding: 2rem;
max-width: 800px;
margin: 0 auto;
width: 100%;
}
/* 查询按钮区域 */
.query-section {
text-align: center;
margin-bottom: 2rem;
}
.query-btn {
background: linear-gradient(135deg, #228B22, #32CD32);
color: white;
border: none;
padding: 1rem 2rem;
font-size: 1.1rem;
font-weight: 600;
border-radius: 50px;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 4px 15px rgba(34, 139, 34, 0.3);
display: inline-flex;
align-items: center;
gap: 0.5rem;
min-width: 200px;
justify-content: center;
}
.query-btn:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(34, 139, 34, 0.4);
background: linear-gradient(135deg, #1e7e1e, #2eb82e);
}
.query-btn:active {
transform: translateY(0);
}
.query-btn:disabled {
opacity: 0.6;
cursor: not-allowed;
transform: none;
}
/* 加载动画 */
.loading {
text-align: center;
padding: 2rem;
}
.spinner {
width: 50px;
height: 50px;
border: 4px solid #f3f3f3;
border-top: 4px solid #4a90e2;
border-radius: 50%;
animation: spin 1s linear infinite;
margin: 0 auto 1rem;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.loading p {
color: #666;
font-size: 1rem;
}
/* IP信息卡片 */
.ip-info {
animation: fadeInUp 0.6s ease;
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.ip-card {
background: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(10px);
border-radius: 20px;
padding: 2rem;
margin-bottom: 2rem;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
}
.ip-header {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 1.5rem;
padding-bottom: 1rem;
border-bottom: 2px solid #f0f0f0;
}
.ip-header i {
font-size: 1.5rem;
color: #4a90e2;
}
.ip-header h2 {
font-size: 1.5rem;
font-weight: 600;
color: #333;
}
.ip-display {
display: flex;
align-items: center;
justify-content: center;
gap: 1rem;
margin-bottom: 1.5rem;
padding: 1.5rem;
background: linear-gradient(135deg, rgba(74, 144, 226, 0.1), rgba(80, 200, 120, 0.1));
border-radius: 15px;
border: 2px solid rgba(74, 144, 226, 0.2);
}
.ip-address {
font-size: 2rem;
font-weight: 700;
font-family: 'Courier New', monospace;
color: #2c3e50;
background: linear-gradient(135deg, #4a90e2, #50c878);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.copy-btn {
background: #4a90e2;
color: white;
border: none;
padding: 0.5rem;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s ease;
font-size: 1rem;
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
}
.copy-btn:hover {
background: #3a7bc8;
transform: scale(1.1);
}
.ip-details {
display: grid;
gap: 1rem;
}
.detail-item {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.75rem;
background: rgba(248, 249, 250, 0.8);
border-radius: 10px;
transition: all 0.3s ease;
}
.detail-item:hover {
background: rgba(74, 144, 226, 0.1);
transform: translateX(5px);
}
.detail-item i {
color: #4a90e2;
width: 20px;
text-align: center;
}
.detail-item .label {
font-weight: 600;
color: #555;
min-width: 80px;
}
.detail-item .value {
color: #333;
font-weight: 500;
}
/* IP地址说明区域 */
.ip-explanation {
background: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(10px);
border-radius: 20px;
padding: 2rem;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
}
.ip-explanation h3 {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 1rem;
font-size: 1.3rem;
color: #333;
}
.ip-explanation h3 i {
color: #4a90e2;
}
.ip-explanation p {
color: #666;
line-height: 1.8;
margin-bottom: 1.5rem;
}
.features {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 1rem;
}
.feature-item {
display: flex;
align-items: flex-start;
gap: 1rem;
padding: 1rem;
background: rgba(248, 249, 250, 0.8);
border-radius: 12px;
transition: all 0.3s ease;
}
.feature-item:hover {
background: rgba(74, 144, 226, 0.1);
transform: translateY(-2px);
}
.feature-item i {
color: #4a90e2;
font-size: 1.5rem;
margin-top: 0.2rem;
}
.feature-item h4 {
font-size: 1rem;
font-weight: 600;
color: #333;
margin-bottom: 0.3rem;
}
.feature-item p {
font-size: 0.9rem;
color: #666;
line-height: 1.5;
margin: 0;
}
/* 错误信息 */
.error-message {
text-align: center;
padding: 2rem;
background: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(10px);
border-radius: 20px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
border: 1px solid rgba(255, 99, 99, 0.3);
animation: fadeInUp 0.6s ease;
}
.error-message i {
font-size: 3rem;
color: #ff6b6b;
margin-bottom: 1rem;
}
.error-message p {
color: #666;
font-size: 1.1rem;
margin-bottom: 1.5rem;
}
.retry-btn {
background: #ff6b6b;
color: white;
border: none;
padding: 0.75rem 1.5rem;
font-size: 1rem;
font-weight: 600;
border-radius: 25px;
cursor: pointer;
transition: all 0.3s ease;
}
.retry-btn:hover {
background: #ff5252;
transform: translateY(-2px);
box-shadow: 0 4px 15px rgba(255, 107, 107, 0.3);
}
/* 页脚 */
.footer {
text-align: center;
padding: 2rem;
background: rgba(248, 249, 250, 0.8);
backdrop-filter: blur(10px);
border-top: 1px solid rgba(255, 255, 255, 0.2);
color: #666;
font-size: 0.9rem;
}
/* 响应式设计 */
@media (max-width: 768px) {
.header {
padding: 2rem 1rem 1.5rem;
}
.header h1 {
font-size: 2rem;
}
.main-content {
padding: 1rem;
}
.ip-card, .ip-explanation {
padding: 1.5rem;
}
.ip-address {
font-size: 1.5rem;
}
.ip-display {
flex-direction: column;
gap: 1rem;
}
.features {
grid-template-columns: 1fr;
}
.detail-item {
flex-direction: column;
align-items: flex-start;
gap: 0.3rem;
}
.detail-item .label {
min-width: auto;
}
}
@media (max-width: 480px) {
.header h1 {
font-size: 1.8rem;
}
.query-btn {
padding: 0.875rem 1.5rem;
font-size: 1rem;
min-width: 180px;
}
.ip-address {
font-size: 1.3rem;
}
.ip-card, .ip-explanation {
padding: 1rem;
}
}

View File

@@ -1,131 +1,131 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>公网IP地址查询</title>
<link rel="stylesheet" href="css/style.css">
<link rel="stylesheet" href="css/background.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
</head>
<body>
<div class="container">
<!-- 头部 -->
<header class="header">
<h1><i class="fas fa-globe"></i> 公网IP地址查询</h1>
<p class="subtitle">快速获取您的公网IP地址信息</p>
</header>
<!-- 主要内容区域 -->
<main class="main-content">
<!-- 查询按钮区域 -->
<div class="query-section">
<button id="queryBtn" class="query-btn">
<i class="fas fa-search"></i>
<span>查询我的IP地址</span>
</button>
</div>
<!-- 加载动画 -->
<div id="loading" class="loading" style="display: none;">
<div class="spinner"></div>
<p>正在获取IP地址信息...</p>
</div>
<!-- IP信息展示区域 -->
<div id="ip-info" class="ip-info" style="display: none;">
<div class="ip-card">
<div class="ip-header">
<i class="fas fa-network-wired"></i>
<h2>您的公网IP地址</h2>
</div>
<div class="ip-display">
<span id="ip-address" class="ip-address">---.---.---.---</span>
<button id="copyBtn" class="copy-btn" title="复制IP地址">
<i class="fas fa-copy"></i>
</button>
</div>
<div class="ip-details">
<div class="detail-item">
<i class="fas fa-clock"></i>
<span class="label">查询时间:</span>
<span id="query-time" class="value">--</span>
</div>
<div class="detail-item">
<i class="fas fa-map-marker-alt"></i>
<span class="label">位置信息:</span>
<span id="location" class="value">--</span>
</div>
<div class="detail-item">
<i class="fas fa-building"></i>
<span class="label">网络服务商:</span>
<span id="isp" class="value">--</span>
</div>
<div class="detail-item">
<i class="fas fa-flag"></i>
<span class="label">国家:</span>
<span id="country" class="value">--</span>
</div>
<div class="detail-item">
<i class="fas fa-map"></i>
<span class="label">地区:</span>
<span id="region" class="value">--</span>
</div>
<div class="detail-item">
<i class="fas fa-city"></i>
<span class="label">城市:</span>
<span id="city" class="value">--</span>
</div>
<div class="detail-item">
<i class="fas fa-clock"></i>
<span class="label">时区:</span>
<span id="timezone" class="value">--</span>
</div>
</div>
</div>
<!-- IP地址信息说明 -->
<div class="ip-explanation">
<h3><i class="fas fa-info-circle"></i> 什么是公网IP地址</h3>
<p>公网IP地址是您的设备在互联网上的唯一标识符由您的网络服务提供商(ISP)分配。通过这个地址,互联网上的其他设备可以找到并与您的设备通信。</p>
<div class="features">
<div class="feature-item">
<i class="fas fa-shield-alt"></i>
<div>
<h4>隐私保护</h4>
<p>了解您的IP地址有助于保护网络隐私</p>
</div>
</div>
<div class="feature-item">
<i class="fas fa-map-marker-alt"></i>
<div>
<h4>地理位置</h4>
<p>IP地址可以大致确定您的地理位置</p>
</div>
</div>
<div class="feature-item">
<i class="fas fa-cogs"></i>
<div>
<h4>网络配置</h4>
<p>用于网络故障排除和配置</p>
</div>
</div>
</div>
</div>
</div>
<!-- 错误信息 -->
<div id="error-message" class="error-message" style="display: none;">
<i class="fas fa-exclamation-triangle"></i>
<p>获取IP地址失败请检查网络连接或稍后重试</p>
<button id="retryBtn" class="retry-btn">重试</button>
</div>
</main>
<!-- 页脚 -->
</div>
<script src="js/script.js"></script>
</body>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>公网IP地址查询</title>
<link rel="stylesheet" href="css/style.css">
<link rel="stylesheet" href="css/background.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
</head>
<body>
<div class="container">
<!-- 头部 -->
<header class="header">
<h1><i class="fas fa-globe"></i> 公网IP地址查询</h1>
<p class="subtitle">快速获取您的公网IP地址信息</p>
</header>
<!-- 主要内容区域 -->
<main class="main-content">
<!-- 查询按钮区域 -->
<div class="query-section">
<button id="queryBtn" class="query-btn">
<i class="fas fa-search"></i>
<span>查询我的IP地址</span>
</button>
</div>
<!-- 加载动画 -->
<div id="loading" class="loading" style="display: none;">
<div class="spinner"></div>
<p>正在获取IP地址信息...</p>
</div>
<!-- IP信息展示区域 -->
<div id="ip-info" class="ip-info" style="display: none;">
<div class="ip-card">
<div class="ip-header">
<i class="fas fa-network-wired"></i>
<h2>您的公网IP地址</h2>
</div>
<div class="ip-display">
<span id="ip-address" class="ip-address">---.---.---.---</span>
<button id="copyBtn" class="copy-btn" title="复制IP地址">
<i class="fas fa-copy"></i>
</button>
</div>
<div class="ip-details">
<div class="detail-item">
<i class="fas fa-clock"></i>
<span class="label">查询时间:</span>
<span id="query-time" class="value">--</span>
</div>
<div class="detail-item">
<i class="fas fa-map-marker-alt"></i>
<span class="label">位置信息:</span>
<span id="location" class="value">--</span>
</div>
<div class="detail-item">
<i class="fas fa-building"></i>
<span class="label">网络服务商:</span>
<span id="isp" class="value">--</span>
</div>
<div class="detail-item">
<i class="fas fa-flag"></i>
<span class="label">国家:</span>
<span id="country" class="value">--</span>
</div>
<div class="detail-item">
<i class="fas fa-map"></i>
<span class="label">地区:</span>
<span id="region" class="value">--</span>
</div>
<div class="detail-item">
<i class="fas fa-city"></i>
<span class="label">城市:</span>
<span id="city" class="value">--</span>
</div>
<div class="detail-item">
<i class="fas fa-clock"></i>
<span class="label">时区:</span>
<span id="timezone" class="value">--</span>
</div>
</div>
</div>
<!-- IP地址信息说明 -->
<div class="ip-explanation">
<h3><i class="fas fa-info-circle"></i> 什么是公网IP地址</h3>
<p>公网IP地址是您的设备在互联网上的唯一标识符由您的网络服务提供商(ISP)分配。通过这个地址,互联网上的其他设备可以找到并与您的设备通信。</p>
<div class="features">
<div class="feature-item">
<i class="fas fa-shield-alt"></i>
<div>
<h4>隐私保护</h4>
<p>了解您的IP地址有助于保护网络隐私</p>
</div>
</div>
<div class="feature-item">
<i class="fas fa-map-marker-alt"></i>
<div>
<h4>地理位置</h4>
<p>IP地址可以大致确定您的地理位置</p>
</div>
</div>
<div class="feature-item">
<i class="fas fa-cogs"></i>
<div>
<h4>网络配置</h4>
<p>用于网络故障排除和配置</p>
</div>
</div>
</div>
</div>
</div>
<!-- 错误信息 -->
<div id="error-message" class="error-message" style="display: none;">
<i class="fas fa-exclamation-triangle"></i>
<p>获取IP地址失败请检查网络连接或稍后重试</p>
<button id="retryBtn" class="retry-btn">重试</button>
</div>
</main>
<!-- 页脚 -->
</div>
<script src="js/script.js"></script>
</body>
</html>

View File

@@ -1,343 +1,343 @@
// 公网IP地址查询应用
class IPQueryApp {
constructor() {
this.apiEndpoint = 'https://60s.api.shumengya.top/v2/ip';
this.init();
}
// 初始化应用
init() {
this.bindEvents();
this.createParticles();
this.createBackgroundElements();
console.log('IP查询应用初始化完成');
}
// 绑定事件
bindEvents() {
const queryBtn = document.getElementById('queryBtn');
const retryBtn = document.getElementById('retryBtn');
const copyBtn = document.getElementById('copyBtn');
if (queryBtn) {
queryBtn.addEventListener('click', () => this.queryIP());
}
if (retryBtn) {
retryBtn.addEventListener('click', () => this.queryIP());
}
if (copyBtn) {
copyBtn.addEventListener('click', () => this.copyIP());
}
// 页面加载完成后自动查询一次
document.addEventListener('DOMContentLoaded', () => {
setTimeout(() => this.queryIP(), 500);
});
}
// 创建浮动粒子
createParticles() {
const particlesContainer = document.createElement('div');
particlesContainer.className = 'particles';
document.body.appendChild(particlesContainer);
for (let i = 0; i < 10; i++) {
const particle = document.createElement('div');
particle.className = 'particle';
particlesContainer.appendChild(particle);
}
}
// 创建背景元素
createBackgroundElements() {
// 创建网格背景
const gridBackground = document.createElement('div');
gridBackground.className = 'grid-background';
document.body.appendChild(gridBackground);
// 创建光晕效果
const glowEffect = document.createElement('div');
glowEffect.className = 'glow-effect';
document.body.appendChild(glowEffect);
}
// 显示加载状态
showLoading() {
const loading = document.getElementById('loading');
const ipInfo = document.getElementById('ipInfo');
const errorMessage = document.getElementById('errorMessage');
const queryBtn = document.getElementById('queryBtn');
if (loading) loading.style.display = 'block';
if (ipInfo) ipInfo.style.display = 'none';
if (errorMessage) errorMessage.style.display = 'none';
if (queryBtn) {
queryBtn.disabled = true;
queryBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> 查询中...';
}
}
// 隐藏加载状态
hideLoading() {
const loading = document.getElementById('loading');
const queryBtn = document.getElementById('queryBtn');
if (loading) loading.style.display = 'none';
if (queryBtn) {
queryBtn.disabled = false;
queryBtn.innerHTML = '<i class="fas fa-search"></i> 查询我的IP';
}
}
// 显示错误信息
showError(message) {
const errorMessage = document.getElementById('error-message');
const ipInfo = document.getElementById('ip-info');
if (errorMessage) {
errorMessage.style.display = 'block';
const errorText = errorMessage.querySelector('p');
if (errorText) errorText.textContent = message || '获取IP信息失败请稍后重试';
}
if (ipInfo) ipInfo.style.display = 'none';
this.hideLoading();
}
// 查询IP地址
async queryIP() {
try {
this.showLoading();
console.log('开始查询IP地址...');
const response = await fetch(this.apiEndpoint, {
method: 'GET',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
});
console.log('API响应状态:', response.status);
if (!response.ok) {
throw new Error(`HTTP错误: ${response.status}`);
}
const data = await response.json();
console.log('API返回数据:', data);
if (data.code === 200 && data.data) {
this.displayIPInfo(data.data);
} else {
throw new Error(data.message || '获取IP信息失败');
}
} catch (error) {
console.error('查询IP失败:', error);
this.showError(error.message);
}
}
// 显示IP信息
displayIPInfo(data) {
const ipInfo = document.getElementById('ip-info');
const errorMessage = document.getElementById('error-message');
// 更新IP地址显示
const ipAddressElement = document.getElementById('ip-address');
if (ipAddressElement && data.ip) {
ipAddressElement.textContent = data.ip;
}
// 更新查询时间
const queryTimeElement = document.getElementById('query-time');
if (queryTimeElement) {
const now = new Date();
queryTimeElement.textContent = now.toLocaleString('zh-CN');
}
// 更新详细信息 - 只显示API提供的数据
if (data.location) this.updateDetailItem('location', data.location);
else this.hideDetailItem('location');
if (data.isp) this.updateDetailItem('isp', data.isp);
else this.hideDetailItem('isp');
if (data.country) this.updateDetailItem('country', data.country);
else this.hideDetailItem('country');
if (data.region) this.updateDetailItem('region', data.region);
else this.hideDetailItem('region');
if (data.city) this.updateDetailItem('city', data.city);
else this.hideDetailItem('city');
if (data.timezone) this.updateDetailItem('timezone', data.timezone);
else this.hideDetailItem('timezone');
// 显示IP信息隐藏错误信息
if (ipInfo) ipInfo.style.display = 'block';
if (errorMessage) errorMessage.style.display = 'none';
this.hideLoading();
console.log('IP信息显示完成');
}
// 更新详细信息项
updateDetailItem(id, value) {
const element = document.getElementById(id);
if (element) {
element.textContent = value;
// 显示对应的详细信息行
const detailRow = element.closest('.detail-item');
if (detailRow) {
detailRow.style.display = 'flex';
}
}
}
// 隐藏详细信息项
hideDetailItem(id) {
const element = document.getElementById(id);
if (element) {
// 隐藏整个详细信息行
const detailRow = element.closest('.detail-item');
if (detailRow) {
detailRow.style.display = 'none';
}
}
}
// 复制IP地址
async copyIP() {
const ipAddressElement = document.getElementById('ip-address');
const copyBtn = document.getElementById('copyBtn');
if (!ipAddressElement || !ipAddressElement.textContent) {
this.showToast('没有可复制的IP地址', 'error');
return;
}
try {
await navigator.clipboard.writeText(ipAddressElement.textContent);
// 更新按钮状态
if (copyBtn) {
const originalHTML = copyBtn.innerHTML;
copyBtn.innerHTML = '<i class="fas fa-check"></i>';
copyBtn.style.background = '#50c878';
setTimeout(() => {
copyBtn.innerHTML = originalHTML;
copyBtn.style.background = '#4a90e2';
}, 1500);
}
this.showToast('IP地址已复制到剪贴板', 'success');
console.log('IP地址已复制:', ipAddressElement.textContent);
} catch (error) {
console.error('复制失败:', error);
this.showToast('复制失败,请手动选择复制', 'error');
}
}
// 显示提示消息
showToast(message, type = 'info') {
// 移除已存在的toast
const existingToast = document.querySelector('.toast');
if (existingToast) {
existingToast.remove();
}
// 创建新的toast
const toast = document.createElement('div');
toast.className = `toast toast-${type}`;
toast.innerHTML = `
<i class="fas fa-${type === 'success' ? 'check-circle' : type === 'error' ? 'exclamation-circle' : 'info-circle'}"></i>
<span>${message}</span>
`;
// 添加toast样式
toast.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: ${type === 'success' ? '#50c878' : type === 'error' ? '#ff6b6b' : '#4a90e2'};
color: white;
padding: 1rem 1.5rem;
border-radius: 10px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
z-index: 1000;
display: flex;
align-items: center;
gap: 0.5rem;
font-weight: 500;
animation: slideInRight 0.3s ease;
max-width: 300px;
`;
// 添加动画样式
const style = document.createElement('style');
style.textContent = `
@keyframes slideInRight {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
@keyframes slideOutRight {
from {
transform: translateX(0);
opacity: 1;
}
to {
transform: translateX(100%);
opacity: 0;
}
}
`;
document.head.appendChild(style);
document.body.appendChild(toast);
// 3秒后自动移除
setTimeout(() => {
toast.style.animation = 'slideOutRight 0.3s ease';
setTimeout(() => {
if (toast.parentNode) {
toast.remove();
}
if (style.parentNode) {
style.remove();
}
}, 300);
}, 3000);
}
// 格式化时间
formatTime(timestamp) {
const date = new Date(timestamp);
return date.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
}
}
// 初始化应用
const app = new IPQueryApp();
// 导出到全局作用域(用于调试)
// 公网IP地址查询应用
class IPQueryApp {
constructor() {
this.apiEndpoint = 'https://60s.api.shumengya.top/v2/ip';
this.init();
}
// 初始化应用
init() {
this.bindEvents();
this.createParticles();
this.createBackgroundElements();
console.log('IP查询应用初始化完成');
}
// 绑定事件
bindEvents() {
const queryBtn = document.getElementById('queryBtn');
const retryBtn = document.getElementById('retryBtn');
const copyBtn = document.getElementById('copyBtn');
if (queryBtn) {
queryBtn.addEventListener('click', () => this.queryIP());
}
if (retryBtn) {
retryBtn.addEventListener('click', () => this.queryIP());
}
if (copyBtn) {
copyBtn.addEventListener('click', () => this.copyIP());
}
// 页面加载完成后自动查询一次
document.addEventListener('DOMContentLoaded', () => {
setTimeout(() => this.queryIP(), 500);
});
}
// 创建浮动粒子
createParticles() {
const particlesContainer = document.createElement('div');
particlesContainer.className = 'particles';
document.body.appendChild(particlesContainer);
for (let i = 0; i < 10; i++) {
const particle = document.createElement('div');
particle.className = 'particle';
particlesContainer.appendChild(particle);
}
}
// 创建背景元素
createBackgroundElements() {
// 创建网格背景
const gridBackground = document.createElement('div');
gridBackground.className = 'grid-background';
document.body.appendChild(gridBackground);
// 创建光晕效果
const glowEffect = document.createElement('div');
glowEffect.className = 'glow-effect';
document.body.appendChild(glowEffect);
}
// 显示加载状态
showLoading() {
const loading = document.getElementById('loading');
const ipInfo = document.getElementById('ipInfo');
const errorMessage = document.getElementById('errorMessage');
const queryBtn = document.getElementById('queryBtn');
if (loading) loading.style.display = 'block';
if (ipInfo) ipInfo.style.display = 'none';
if (errorMessage) errorMessage.style.display = 'none';
if (queryBtn) {
queryBtn.disabled = true;
queryBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> 查询中...';
}
}
// 隐藏加载状态
hideLoading() {
const loading = document.getElementById('loading');
const queryBtn = document.getElementById('queryBtn');
if (loading) loading.style.display = 'none';
if (queryBtn) {
queryBtn.disabled = false;
queryBtn.innerHTML = '<i class="fas fa-search"></i> 查询我的IP';
}
}
// 显示错误信息
showError(message) {
const errorMessage = document.getElementById('error-message');
const ipInfo = document.getElementById('ip-info');
if (errorMessage) {
errorMessage.style.display = 'block';
const errorText = errorMessage.querySelector('p');
if (errorText) errorText.textContent = message || '获取IP信息失败请稍后重试';
}
if (ipInfo) ipInfo.style.display = 'none';
this.hideLoading();
}
// 查询IP地址
async queryIP() {
try {
this.showLoading();
console.log('开始查询IP地址...');
const response = await fetch(this.apiEndpoint, {
method: 'GET',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
});
console.log('API响应状态:', response.status);
if (!response.ok) {
throw new Error(`HTTP错误: ${response.status}`);
}
const data = await response.json();
console.log('API返回数据:', data);
if (data.code === 200 && data.data) {
this.displayIPInfo(data.data);
} else {
throw new Error(data.message || '获取IP信息失败');
}
} catch (error) {
console.error('查询IP失败:', error);
this.showError(error.message);
}
}
// 显示IP信息
displayIPInfo(data) {
const ipInfo = document.getElementById('ip-info');
const errorMessage = document.getElementById('error-message');
// 更新IP地址显示
const ipAddressElement = document.getElementById('ip-address');
if (ipAddressElement && data.ip) {
ipAddressElement.textContent = data.ip;
}
// 更新查询时间
const queryTimeElement = document.getElementById('query-time');
if (queryTimeElement) {
const now = new Date();
queryTimeElement.textContent = now.toLocaleString('zh-CN');
}
// 更新详细信息 - 只显示API提供的数据
if (data.location) this.updateDetailItem('location', data.location);
else this.hideDetailItem('location');
if (data.isp) this.updateDetailItem('isp', data.isp);
else this.hideDetailItem('isp');
if (data.country) this.updateDetailItem('country', data.country);
else this.hideDetailItem('country');
if (data.region) this.updateDetailItem('region', data.region);
else this.hideDetailItem('region');
if (data.city) this.updateDetailItem('city', data.city);
else this.hideDetailItem('city');
if (data.timezone) this.updateDetailItem('timezone', data.timezone);
else this.hideDetailItem('timezone');
// 显示IP信息隐藏错误信息
if (ipInfo) ipInfo.style.display = 'block';
if (errorMessage) errorMessage.style.display = 'none';
this.hideLoading();
console.log('IP信息显示完成');
}
// 更新详细信息项
updateDetailItem(id, value) {
const element = document.getElementById(id);
if (element) {
element.textContent = value;
// 显示对应的详细信息行
const detailRow = element.closest('.detail-item');
if (detailRow) {
detailRow.style.display = 'flex';
}
}
}
// 隐藏详细信息项
hideDetailItem(id) {
const element = document.getElementById(id);
if (element) {
// 隐藏整个详细信息行
const detailRow = element.closest('.detail-item');
if (detailRow) {
detailRow.style.display = 'none';
}
}
}
// 复制IP地址
async copyIP() {
const ipAddressElement = document.getElementById('ip-address');
const copyBtn = document.getElementById('copyBtn');
if (!ipAddressElement || !ipAddressElement.textContent) {
this.showToast('没有可复制的IP地址', 'error');
return;
}
try {
await navigator.clipboard.writeText(ipAddressElement.textContent);
// 更新按钮状态
if (copyBtn) {
const originalHTML = copyBtn.innerHTML;
copyBtn.innerHTML = '<i class="fas fa-check"></i>';
copyBtn.style.background = '#50c878';
setTimeout(() => {
copyBtn.innerHTML = originalHTML;
copyBtn.style.background = '#4a90e2';
}, 1500);
}
this.showToast('IP地址已复制到剪贴板', 'success');
console.log('IP地址已复制:', ipAddressElement.textContent);
} catch (error) {
console.error('复制失败:', error);
this.showToast('复制失败,请手动选择复制', 'error');
}
}
// 显示提示消息
showToast(message, type = 'info') {
// 移除已存在的toast
const existingToast = document.querySelector('.toast');
if (existingToast) {
existingToast.remove();
}
// 创建新的toast
const toast = document.createElement('div');
toast.className = `toast toast-${type}`;
toast.innerHTML = `
<i class="fas fa-${type === 'success' ? 'check-circle' : type === 'error' ? 'exclamation-circle' : 'info-circle'}"></i>
<span>${message}</span>
`;
// 添加toast样式
toast.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: ${type === 'success' ? '#50c878' : type === 'error' ? '#ff6b6b' : '#4a90e2'};
color: white;
padding: 1rem 1.5rem;
border-radius: 10px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
z-index: 1000;
display: flex;
align-items: center;
gap: 0.5rem;
font-weight: 500;
animation: slideInRight 0.3s ease;
max-width: 300px;
`;
// 添加动画样式
const style = document.createElement('style');
style.textContent = `
@keyframes slideInRight {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
@keyframes slideOutRight {
from {
transform: translateX(0);
opacity: 1;
}
to {
transform: translateX(100%);
opacity: 0;
}
}
`;
document.head.appendChild(style);
document.body.appendChild(toast);
// 3秒后自动移除
setTimeout(() => {
toast.style.animation = 'slideOutRight 0.3s ease';
setTimeout(() => {
if (toast.parentNode) {
toast.remove();
}
if (style.parentNode) {
style.remove();
}
}, 300);
}, 3000);
}
// 格式化时间
formatTime(timestamp) {
const date = new Date(timestamp);
return date.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
}
}
// 初始化应用
const app = new IPQueryApp();
// 导出到全局作用域(用于调试)
window.IPQueryApp = app;

View File

@@ -1,3 +1,3 @@
[
"https://60s.api.shumengya.top/v2/ip"
[
"https://60s.api.shumengya.top/v2/ip"
]

View File

@@ -1,17 +1,17 @@
{
"code": 200,
"message": "获取成功。数据来自官方/权威源头,以确保稳定与实时。开源地址 https://github.com/vikiboss/60s反馈群 595941841",
"data": {
"ip": "2401:b60:16:83::"
}
}
// 注意此API只返回IP地址不包含以下信息
// - location (位置信息)
// - isp (网络服务商)
// - country (国家)
// - region (地区)
// - city (城市)
// - timezone (时区)
//
{
"code": 200,
"message": "获取成功。数据来自官方/权威源头,以确保稳定与实时。开源地址 https://github.com/vikiboss/60s反馈群 595941841",
"data": {
"ip": "2401:b60:16:83::"
}
}
// 注意此API只返回IP地址不包含以下信息
// - location (位置信息)
// - isp (网络服务商)
// - country (国家)
// - region (地区)
// - city (城市)
// - timezone (时区)
//
// 使API

View File

@@ -1,108 +1,108 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>🌙农历信息查询</title>
<link rel="stylesheet" href="css/background.css">
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<!-- 动态调节遮罩层 -->
<div class="adaptive-overlay"></div>
<!-- 流星效果容器 -->
<div class="meteor-container">
<div class="meteor"></div>
<div class="meteor"></div>
<div class="meteor"></div>
<div class="meteor"></div>
<div class="meteor"></div>
<div class="meteor"></div>
</div>
<!-- 金色粒子效果容器 -->
<div class="golden-particles">
<div class="particle"></div>
<div class="particle"></div>
<div class="particle"></div>
<div class="particle"></div>
<div class="particle"></div>
<div class="particle"></div>
<div class="particle"></div>
<div class="particle"></div>
<div class="particle"></div>
</div>
<!-- 麦穗飘舞特效 -->
<div class="wheat-floating">
<div class="wheat-particle"></div>
<div class="wheat-particle"></div>
<div class="wheat-particle"></div>
<div class="wheat-particle"></div>
<div class="wheat-particle"></div>
<div class="wheat-particle"></div>
<div class="wheat-particle"></div>
<div class="wheat-particle"></div>
</div>
<div class="container">
<header class="header">
<h1 class="title">🌙农历信息查询</h1>
<p class="subtitle">传统文化 · 时光转换 · 节气查询</p>
<div class="date-selector">
<div class="input-group">
<label for="dateInput" class="input-label">
<span class="label-icon">📅</span>
选择日期
</label>
<input type="date" id="dateInput" class="date-input" />
</div>
<button id="queryBtn" class="query-btn">
<span class="btn-icon">🔍</span>
查询农历
</button>
</div>
<div class="update-time">
<span class="time-icon"></span>
<span id="updateTime">等待查询...</span>
</div>
</header>
<div class="loading" id="loading" style="display: none;">
<div class="loading-content">
<div class="glass-spinner"></div>
<div class="loading-text">
<span class="loading-emoji">🔮</span>
<p>正在查询农历信息...</p>
<div class="loading-dots">
<span></span>
<span></span>
<span></span>
</div>
</div>
</div>
</div>
<div class="lunar-info" id="lunarInfo" style="display: none;">
<!-- 农历信息将动态生成 -->
</div>
<div class="error-message" id="errorMessage" style="display: none;">
<div class="error-content">
<div class="error-icon">😔</div>
<h3>查询失败了</h3>
<p>无法获取农历信息,请稍后重试</p>
<button onclick="queryLunarInfo()" class="retry-btn">
<span>🔄</span>
重新查询
</button>
</div>
</div>
<script src="js/script.js"></script>
</body>
</html>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>🌙农历信息查询</title>
<link rel="stylesheet" href="css/background.css">
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<!-- 动态调节遮罩层 -->
<div class="adaptive-overlay"></div>
<!-- 流星效果容器 -->
<div class="meteor-container">
<div class="meteor"></div>
<div class="meteor"></div>
<div class="meteor"></div>
<div class="meteor"></div>
<div class="meteor"></div>
<div class="meteor"></div>
</div>
<!-- 金色粒子效果容器 -->
<div class="golden-particles">
<div class="particle"></div>
<div class="particle"></div>
<div class="particle"></div>
<div class="particle"></div>
<div class="particle"></div>
<div class="particle"></div>
<div class="particle"></div>
<div class="particle"></div>
<div class="particle"></div>
</div>
<!-- 麦穗飘舞特效 -->
<div class="wheat-floating">
<div class="wheat-particle"></div>
<div class="wheat-particle"></div>
<div class="wheat-particle"></div>
<div class="wheat-particle"></div>
<div class="wheat-particle"></div>
<div class="wheat-particle"></div>
<div class="wheat-particle"></div>
<div class="wheat-particle"></div>
</div>
<div class="container">
<header class="header">
<h1 class="title">🌙农历信息查询</h1>
<p class="subtitle">传统文化 · 时光转换 · 节气查询</p>
<div class="date-selector">
<div class="input-group">
<label for="dateInput" class="input-label">
<span class="label-icon">📅</span>
选择日期
</label>
<input type="date" id="dateInput" class="date-input" />
</div>
<button id="queryBtn" class="query-btn">
<span class="btn-icon">🔍</span>
查询农历
</button>
</div>
<div class="update-time">
<span class="time-icon"></span>
<span id="updateTime">等待查询...</span>
</div>
</header>
<div class="loading" id="loading" style="display: none;">
<div class="loading-content">
<div class="glass-spinner"></div>
<div class="loading-text">
<span class="loading-emoji">🔮</span>
<p>正在查询农历信息...</p>
<div class="loading-dots">
<span></span>
<span></span>
<span></span>
</div>
</div>
</div>
</div>
<div class="lunar-info" id="lunarInfo" style="display: none;">
<!-- 农历信息将动态生成 -->
</div>
<div class="error-message" id="errorMessage" style="display: none;">
<div class="error-content">
<div class="error-icon">😔</div>
<h3>查询失败了</h3>
<p>无法获取农历信息,请稍后重试</p>
<button onclick="queryLunarInfo()" class="retry-btn">
<span>🔄</span>
重新查询
</button>
</div>
</div>
<script src="js/script.js"></script>
</body>
</html>

View File

@@ -1,475 +1,475 @@
// API接口列表
const API_ENDPOINTS = [
"https://60s.api.shumengya.top",
];
// 当前使用的API索引
let currentApiIndex = 0;
// DOM元素
const loadingElement = document.getElementById('loading');
const lunarInfoElement = document.getElementById('lunarInfo');
const errorMessageElement = document.getElementById('errorMessage');
const updateTimeElement = document.getElementById('updateTime');
const dateInput = document.getElementById('dateInput');
const queryBtn = document.getElementById('queryBtn');
// 页面加载完成后初始化
document.addEventListener('DOMContentLoaded', function() {
initializePage();
});
// 初始化页面
function initializePage() {
// 设置默认日期为今天
const today = new Date();
const dateString = today.toISOString().split('T')[0];
dateInput.value = dateString;
// 绑定事件
queryBtn.addEventListener('click', queryLunarInfo);
dateInput.addEventListener('change', queryLunarInfo);
// 自动查询当天信息
queryLunarInfo();
}
// 查询农历信息
async function queryLunarInfo() {
const selectedDate = dateInput.value;
if (!selectedDate) {
showError('请选择查询日期');
return;
}
showLoading();
hideError();
hideLunarInfo();
try {
const data = await fetchLunarData(selectedDate);
displayLunarInfo(data.data);
updateQueryTime();
} catch (error) {
console.error('查询失败:', error);
showError('查询农历信息失败,请稍后重试');
}
hideLoading();
}
// 获取农历数据
async function fetchLunarData(date) {
for (let i = 0; i < API_ENDPOINTS.length; i++) {
const apiUrl = API_ENDPOINTS[currentApiIndex];
try {
const response = await fetch(`${apiUrl}/v2/lunar?date=${date}`, {
method: 'GET',
headers: {
'Accept': 'application/json',
},
timeout: 10000
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const data = await response.json();
if (data.code === 200 && data.data) {
return data;
} else {
throw new Error('数据格式错误');
}
} catch (error) {
console.error(`API ${apiUrl} 请求失败:`, error);
currentApiIndex = (currentApiIndex + 1) % API_ENDPOINTS.length;
if (i === API_ENDPOINTS.length - 1) {
throw new Error('所有API接口都无法访问');
}
}
}
}
// 显示农历信息
function displayLunarInfo(lunarData) {
lunarInfoElement.innerHTML = `
<div class="info-card">
<div class="card-header">
<div class="card-icon">📅</div>
<div class="card-title">公历信息</div>
</div>
<div class="card-content">
<div class="info-item">
<div class="item-icon">🗓️</div>
<div class="item-label">公历日期</div>
<div class="item-value">${lunarData.solar.year}${String(lunarData.solar.month).padStart(2, '0')}${String(lunarData.solar.day).padStart(2, '0')}日</div>
</div>
<div class="info-item">
<div class="item-icon">🌍</div>
<div class="item-label">星期</div>
<div class="item-value">${lunarData.solar.week_desc}</div>
</div>
<div class="info-item">
<div class="item-icon">🍂</div>
<div class="item-label">季节</div>
<div class="item-value">${lunarData.solar.season_name_desc}</div>
</div>
<div class="info-item">
<div class="item-icon">⭐</div>
<div class="item-label">星座</div>
<div class="item-value">${lunarData.constellation.name}</div>
</div>
</div>
</div>
<div class="info-card">
<div class="card-header">
<div class="card-icon">🌙</div>
<div class="card-title">农历信息</div>
</div>
<div class="card-content">
<div class="info-item">
<div class="item-icon">🏮</div>
<div class="item-label">农历日期</div>
<div class="item-value">${lunarData.lunar.desc_short}</div>
</div>
<div class="info-item">
<div class="item-icon">🐲</div>
<div class="item-label">生肖年</div>
<div class="item-value">${lunarData.zodiac.year}年</div>
</div>
<div class="info-item">
<div class="item-icon">☯️</div>
<div class="item-label">天干地支</div>
<div class="item-value">${lunarData.sixty_cycle.year.name}</div>
</div>
<div class="info-item">
<div class="item-icon">🌙</div>
<div class="item-label">月相</div>
<div class="item-value">${lunarData.phase.name}</div>
</div>
</div>
</div>
<div class="info-card">
<div class="card-header">
<div class="card-icon">🌾</div>
<div class="card-title">节气节日</div>
</div>
<div class="card-content">
<div class="info-item">
<div class="item-icon">🌱</div>
<div class="item-label">当前节气</div>
<div class="item-value">${lunarData.term.stage ? lunarData.term.stage.name : '无节气'}</div>
</div>
<div class="info-item">
<div class="item-icon">🎉</div>
<div class="item-label">法定假日</div>
<div class="item-value">${lunarData.legal_holiday ? lunarData.legal_holiday.name : '无假日'}</div>
</div>
<div class="info-item">
<div class="item-icon">🎊</div>
<div class="item-label">传统节日</div>
<div class="item-value">${lunarData.festival.both_desc || '无特殊节日'}</div>
</div>
<div class="info-item">
<div class="item-icon">📊</div>
<div class="item-label">一年第几天</div>
<div class="item-value">第${lunarData.stats.day_of_year}天</div>
</div>
</div>
</div>
<div class="info-card">
<div class="card-header">
<div class="card-icon">⏰</div>
<div class="card-title">时辰干支</div>
</div>
<div class="card-content">
<div class="info-item">
<div class="item-icon">🕐</div>
<div class="item-label">当前时辰</div>
<div class="item-value">${lunarData.lunar.hour_desc}</div>
</div>
<div class="info-item">
<div class="item-icon">☯️</div>
<div class="item-label">时辰干支</div>
<div class="item-value">${lunarData.sixty_cycle.hour.name}</div>
</div>
<div class="info-item">
<div class="item-icon">🐾</div>
<div class="item-label">时辰生肖</div>
<div class="item-value">${lunarData.zodiac.hour}</div>
</div>
<div class="info-item">
<div class="item-icon">🎵</div>
<div class="item-label">纳音</div>
<div class="item-value">${lunarData.nayin.hour}</div>
</div>
</div>
</div>
<div class="info-card">
<div class="card-header">
<div class="card-icon">📖</div>
<div class="card-title">黄历宜忌</div>
</div>
<div class="card-content">
<div class="info-item">
<div class="item-icon">✅</div>
<div class="item-label">宜</div>
<div class="item-value">${formatTabooText(lunarData.taboo.day.recommends)}</div>
</div>
<div class="info-item">
<div class="item-icon">❌</div>
<div class="item-label">忌</div>
<div class="item-value">${formatTabooText(lunarData.taboo.day.avoids)}</div>
</div>
<div class="info-item">
<div class="item-icon">🕐</div>
<div class="item-label">时辰宜</div>
<div class="item-value">${formatTabooText(lunarData.taboo.hour.recommends)}</div>
</div>
<div class="info-item">
<div class="item-icon">🚫</div>
<div class="item-label">时辰忌</div>
<div class="item-value">${formatTabooText(lunarData.taboo.hour.avoids)}</div>
</div>
</div>
</div>
<div class="info-card">
<div class="card-header">
<div class="card-icon">🌟</div>
<div class="card-title">运势财运</div>
</div>
<div class="card-content">
<div class="info-item">
<div class="item-icon">🍀</div>
<div class="item-label">今日运势</div>
<div class="item-value">${lunarData.fortune.today_luck}</div>
</div>
<div class="info-item">
<div class="item-icon">💼</div>
<div class="item-label">事业运</div>
<div class="item-value">${lunarData.fortune.career}</div>
</div>
<div class="info-item">
<div class="item-icon">💰</div>
<div class="item-label">财运</div>
<div class="item-value">${lunarData.fortune.money}</div>
</div>
<div class="info-item">
<div class="item-icon">💖</div>
<div class="item-label">感情运</div>
<div class="item-value">${lunarData.fortune.love}</div>
</div>
</div>
</div>
<div class="info-card">
<div class="card-header">
<div class="card-icon">📈</div>
<div class="card-title">年度统计</div>
</div>
<div class="card-content">
<div class="info-item">
<div class="item-icon">📊</div>
<div class="item-label">年度进度</div>
<div class="item-value">${lunarData.stats.percents_formatted.year}</div>
</div>
<div class="info-item">
<div class="item-icon">📅</div>
<div class="item-label">本月进度</div>
<div class="item-value">${lunarData.stats.percents_formatted.month}</div>
</div>
</div>
</div>
</div>
</div>
${generateHourlyTaboo(lunarData.taboo.hours)}
`;
showLunarInfo();
}
// 格式化宜忌文本
function formatTabooText(text) {
if (!text) return '无';
return text.replace(/\./g, '、');
}
// 生成十二时辰宜忌
function generateHourlyTaboo(hours) {
if (!hours || hours.length === 0) return '';
const hourCards = hours.map(hour => `
<div class="hour-item">
<div class="hour-name">${hour.hour}</div>
<div class="hour-content">
<div class="hour-recommends">
<span class="hour-label">宜:</span>
<span class="hour-text">${formatTabooText(hour.recommends) || '无'}</span>
</div>
<div class="hour-avoids">
<span class="hour-label">忌:</span>
<span class="hour-text">${formatTabooText(hour.avoids) || '无'}</span>
</div>
</div>
</div>
`).join('');
return `
<div class="info-card hours-card">
<div class="card-header">
<div class="card-icon">⏰</div>
<div class="card-title">十二时辰宜忌</div>
</div>
<div class="card-content">
<div class="hours-grid">
${hourCards}
</div>
</div>
</div>
`;
}
// 更新查询时间
function updateQueryTime() {
const now = new Date();
const timeStr = now.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
updateTimeElement.textContent = `查询时间: ${timeStr}`;
// 添加成功提示
showSuccessMessage('🌙 农历信息已更新');
}
// 显示成功消息
function showSuccessMessage(message) {
// 移除之前的提示
const existingToast = document.querySelector('.success-toast');
if (existingToast) {
existingToast.remove();
}
const toast = document.createElement('div');
toast.className = 'success-toast';
toast.textContent = message;
toast.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: rgba(255, 255, 255, 0.2);
backdrop-filter: blur(15px);
-webkit-backdrop-filter: blur(15px);
color: #1a1a1a;
padding: 12px 20px;
border-radius: 25px;
border: 1px solid rgba(255, 255, 255, 0.3);
box-shadow: 0 8px 32px rgba(31, 38, 135, 0.2);
z-index: 1000;
font-weight: 600;
font-size: 0.9em;
animation: glassToastSlide 0.5s ease-out;
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
`;
document.body.appendChild(toast);
// 3秒后自动移除
setTimeout(() => {
toast.style.animation = 'glassToastSlideOut 0.5s ease-in forwards';
setTimeout(() => toast.remove(), 500);
}, 3000);
}
// 显示加载状态
function showLoading() {
loadingElement.style.display = 'block';
}
// 隐藏加载状态
function hideLoading() {
loadingElement.style.display = 'none';
}
// 显示农历信息
function showLunarInfo() {
lunarInfoElement.style.display = 'block';
}
// 隐藏农历信息
function hideLunarInfo() {
lunarInfoElement.style.display = 'none';
}
// 显示错误信息
function showError(message = '查询失败,请稍后重试') {
errorMessageElement.style.display = 'block';
const errorContent = errorMessageElement.querySelector('.error-content p');
if (errorContent) {
errorContent.textContent = message;
}
}
// 隐藏错误信息
function hideError() {
errorMessageElement.style.display = 'none';
}
// 添加CSS动画到页面
if (!document.querySelector('#toast-styles')) {
const style = document.createElement('style');
style.id = 'toast-styles';
style.textContent = `
@keyframes glassToastSlide {
from {
opacity: 0;
transform: translateX(100px) scale(0.8);
}
to {
opacity: 1;
transform: translateX(0) scale(1);
}
}
@keyframes glassToastSlideOut {
from {
opacity: 1;
transform: translateX(0) scale(1);
}
to {
opacity: 0;
transform: translateX(100px) scale(0.8);
}
}
`;
document.head.appendChild(style);
}
// 键盘快捷键支持
document.addEventListener('keydown', function(e) {
if (e.key === 'Enter') {
e.preventDefault();
queryLunarInfo();
}
if (e.key === 'r' && (e.ctrlKey || e.metaKey)) {
e.preventDefault();
queryLunarInfo();
}
});
// API接口列表
const API_ENDPOINTS = [
"https://60s.api.shumengya.top",
];
// 当前使用的API索引
let currentApiIndex = 0;
// DOM元素
const loadingElement = document.getElementById('loading');
const lunarInfoElement = document.getElementById('lunarInfo');
const errorMessageElement = document.getElementById('errorMessage');
const updateTimeElement = document.getElementById('updateTime');
const dateInput = document.getElementById('dateInput');
const queryBtn = document.getElementById('queryBtn');
// 页面加载完成后初始化
document.addEventListener('DOMContentLoaded', function() {
initializePage();
});
// 初始化页面
function initializePage() {
// 设置默认日期为今天
const today = new Date();
const dateString = today.toISOString().split('T')[0];
dateInput.value = dateString;
// 绑定事件
queryBtn.addEventListener('click', queryLunarInfo);
dateInput.addEventListener('change', queryLunarInfo);
// 自动查询当天信息
queryLunarInfo();
}
// 查询农历信息
async function queryLunarInfo() {
const selectedDate = dateInput.value;
if (!selectedDate) {
showError('请选择查询日期');
return;
}
showLoading();
hideError();
hideLunarInfo();
try {
const data = await fetchLunarData(selectedDate);
displayLunarInfo(data.data);
updateQueryTime();
} catch (error) {
console.error('查询失败:', error);
showError('查询农历信息失败,请稍后重试');
}
hideLoading();
}
// 获取农历数据
async function fetchLunarData(date) {
for (let i = 0; i < API_ENDPOINTS.length; i++) {
const apiUrl = API_ENDPOINTS[currentApiIndex];
try {
const response = await fetch(`${apiUrl}/v2/lunar?date=${date}`, {
method: 'GET',
headers: {
'Accept': 'application/json',
},
timeout: 10000
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const data = await response.json();
if (data.code === 200 && data.data) {
return data;
} else {
throw new Error('数据格式错误');
}
} catch (error) {
console.error(`API ${apiUrl} 请求失败:`, error);
currentApiIndex = (currentApiIndex + 1) % API_ENDPOINTS.length;
if (i === API_ENDPOINTS.length - 1) {
throw new Error('所有API接口都无法访问');
}
}
}
}
// 显示农历信息
function displayLunarInfo(lunarData) {
lunarInfoElement.innerHTML = `
<div class="info-card">
<div class="card-header">
<div class="card-icon">📅</div>
<div class="card-title">公历信息</div>
</div>
<div class="card-content">
<div class="info-item">
<div class="item-icon">🗓️</div>
<div class="item-label">公历日期</div>
<div class="item-value">${lunarData.solar.year}${String(lunarData.solar.month).padStart(2, '0')}${String(lunarData.solar.day).padStart(2, '0')}日</div>
</div>
<div class="info-item">
<div class="item-icon">🌍</div>
<div class="item-label">星期</div>
<div class="item-value">${lunarData.solar.week_desc}</div>
</div>
<div class="info-item">
<div class="item-icon">🍂</div>
<div class="item-label">季节</div>
<div class="item-value">${lunarData.solar.season_name_desc}</div>
</div>
<div class="info-item">
<div class="item-icon">⭐</div>
<div class="item-label">星座</div>
<div class="item-value">${lunarData.constellation.name}</div>
</div>
</div>
</div>
<div class="info-card">
<div class="card-header">
<div class="card-icon">🌙</div>
<div class="card-title">农历信息</div>
</div>
<div class="card-content">
<div class="info-item">
<div class="item-icon">🏮</div>
<div class="item-label">农历日期</div>
<div class="item-value">${lunarData.lunar.desc_short}</div>
</div>
<div class="info-item">
<div class="item-icon">🐲</div>
<div class="item-label">生肖年</div>
<div class="item-value">${lunarData.zodiac.year}年</div>
</div>
<div class="info-item">
<div class="item-icon">☯️</div>
<div class="item-label">天干地支</div>
<div class="item-value">${lunarData.sixty_cycle.year.name}</div>
</div>
<div class="info-item">
<div class="item-icon">🌙</div>
<div class="item-label">月相</div>
<div class="item-value">${lunarData.phase.name}</div>
</div>
</div>
</div>
<div class="info-card">
<div class="card-header">
<div class="card-icon">🌾</div>
<div class="card-title">节气节日</div>
</div>
<div class="card-content">
<div class="info-item">
<div class="item-icon">🌱</div>
<div class="item-label">当前节气</div>
<div class="item-value">${lunarData.term.stage ? lunarData.term.stage.name : '无节气'}</div>
</div>
<div class="info-item">
<div class="item-icon">🎉</div>
<div class="item-label">法定假日</div>
<div class="item-value">${lunarData.legal_holiday ? lunarData.legal_holiday.name : '无假日'}</div>
</div>
<div class="info-item">
<div class="item-icon">🎊</div>
<div class="item-label">传统节日</div>
<div class="item-value">${lunarData.festival.both_desc || '无特殊节日'}</div>
</div>
<div class="info-item">
<div class="item-icon">📊</div>
<div class="item-label">一年第几天</div>
<div class="item-value">第${lunarData.stats.day_of_year}天</div>
</div>
</div>
</div>
<div class="info-card">
<div class="card-header">
<div class="card-icon">⏰</div>
<div class="card-title">时辰干支</div>
</div>
<div class="card-content">
<div class="info-item">
<div class="item-icon">🕐</div>
<div class="item-label">当前时辰</div>
<div class="item-value">${lunarData.lunar.hour_desc}</div>
</div>
<div class="info-item">
<div class="item-icon">☯️</div>
<div class="item-label">时辰干支</div>
<div class="item-value">${lunarData.sixty_cycle.hour.name}</div>
</div>
<div class="info-item">
<div class="item-icon">🐾</div>
<div class="item-label">时辰生肖</div>
<div class="item-value">${lunarData.zodiac.hour}</div>
</div>
<div class="info-item">
<div class="item-icon">🎵</div>
<div class="item-label">纳音</div>
<div class="item-value">${lunarData.nayin.hour}</div>
</div>
</div>
</div>
<div class="info-card">
<div class="card-header">
<div class="card-icon">📖</div>
<div class="card-title">黄历宜忌</div>
</div>
<div class="card-content">
<div class="info-item">
<div class="item-icon">✅</div>
<div class="item-label">宜</div>
<div class="item-value">${formatTabooText(lunarData.taboo.day.recommends)}</div>
</div>
<div class="info-item">
<div class="item-icon">❌</div>
<div class="item-label">忌</div>
<div class="item-value">${formatTabooText(lunarData.taboo.day.avoids)}</div>
</div>
<div class="info-item">
<div class="item-icon">🕐</div>
<div class="item-label">时辰宜</div>
<div class="item-value">${formatTabooText(lunarData.taboo.hour.recommends)}</div>
</div>
<div class="info-item">
<div class="item-icon">🚫</div>
<div class="item-label">时辰忌</div>
<div class="item-value">${formatTabooText(lunarData.taboo.hour.avoids)}</div>
</div>
</div>
</div>
<div class="info-card">
<div class="card-header">
<div class="card-icon">🌟</div>
<div class="card-title">运势财运</div>
</div>
<div class="card-content">
<div class="info-item">
<div class="item-icon">🍀</div>
<div class="item-label">今日运势</div>
<div class="item-value">${lunarData.fortune.today_luck}</div>
</div>
<div class="info-item">
<div class="item-icon">💼</div>
<div class="item-label">事业运</div>
<div class="item-value">${lunarData.fortune.career}</div>
</div>
<div class="info-item">
<div class="item-icon">💰</div>
<div class="item-label">财运</div>
<div class="item-value">${lunarData.fortune.money}</div>
</div>
<div class="info-item">
<div class="item-icon">💖</div>
<div class="item-label">感情运</div>
<div class="item-value">${lunarData.fortune.love}</div>
</div>
</div>
</div>
<div class="info-card">
<div class="card-header">
<div class="card-icon">📈</div>
<div class="card-title">年度统计</div>
</div>
<div class="card-content">
<div class="info-item">
<div class="item-icon">📊</div>
<div class="item-label">年度进度</div>
<div class="item-value">${lunarData.stats.percents_formatted.year}</div>
</div>
<div class="info-item">
<div class="item-icon">📅</div>
<div class="item-label">本月进度</div>
<div class="item-value">${lunarData.stats.percents_formatted.month}</div>
</div>
</div>
</div>
</div>
</div>
${generateHourlyTaboo(lunarData.taboo.hours)}
`;
showLunarInfo();
}
// 格式化宜忌文本
function formatTabooText(text) {
if (!text) return '无';
return text.replace(/\./g, '、');
}
// 生成十二时辰宜忌
function generateHourlyTaboo(hours) {
if (!hours || hours.length === 0) return '';
const hourCards = hours.map(hour => `
<div class="hour-item">
<div class="hour-name">${hour.hour}</div>
<div class="hour-content">
<div class="hour-recommends">
<span class="hour-label">宜:</span>
<span class="hour-text">${formatTabooText(hour.recommends) || '无'}</span>
</div>
<div class="hour-avoids">
<span class="hour-label">忌:</span>
<span class="hour-text">${formatTabooText(hour.avoids) || '无'}</span>
</div>
</div>
</div>
`).join('');
return `
<div class="info-card hours-card">
<div class="card-header">
<div class="card-icon">⏰</div>
<div class="card-title">十二时辰宜忌</div>
</div>
<div class="card-content">
<div class="hours-grid">
${hourCards}
</div>
</div>
</div>
`;
}
// 更新查询时间
function updateQueryTime() {
const now = new Date();
const timeStr = now.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
updateTimeElement.textContent = `查询时间: ${timeStr}`;
// 添加成功提示
showSuccessMessage('🌙 农历信息已更新');
}
// 显示成功消息
function showSuccessMessage(message) {
// 移除之前的提示
const existingToast = document.querySelector('.success-toast');
if (existingToast) {
existingToast.remove();
}
const toast = document.createElement('div');
toast.className = 'success-toast';
toast.textContent = message;
toast.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: rgba(255, 255, 255, 0.2);
backdrop-filter: blur(15px);
-webkit-backdrop-filter: blur(15px);
color: #1a1a1a;
padding: 12px 20px;
border-radius: 25px;
border: 1px solid rgba(255, 255, 255, 0.3);
box-shadow: 0 8px 32px rgba(31, 38, 135, 0.2);
z-index: 1000;
font-weight: 600;
font-size: 0.9em;
animation: glassToastSlide 0.5s ease-out;
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
`;
document.body.appendChild(toast);
// 3秒后自动移除
setTimeout(() => {
toast.style.animation = 'glassToastSlideOut 0.5s ease-in forwards';
setTimeout(() => toast.remove(), 500);
}, 3000);
}
// 显示加载状态
function showLoading() {
loadingElement.style.display = 'block';
}
// 隐藏加载状态
function hideLoading() {
loadingElement.style.display = 'none';
}
// 显示农历信息
function showLunarInfo() {
lunarInfoElement.style.display = 'block';
}
// 隐藏农历信息
function hideLunarInfo() {
lunarInfoElement.style.display = 'none';
}
// 显示错误信息
function showError(message = '查询失败,请稍后重试') {
errorMessageElement.style.display = 'block';
const errorContent = errorMessageElement.querySelector('.error-content p');
if (errorContent) {
errorContent.textContent = message;
}
}
// 隐藏错误信息
function hideError() {
errorMessageElement.style.display = 'none';
}
// 添加CSS动画到页面
if (!document.querySelector('#toast-styles')) {
const style = document.createElement('style');
style.id = 'toast-styles';
style.textContent = `
@keyframes glassToastSlide {
from {
opacity: 0;
transform: translateX(100px) scale(0.8);
}
to {
opacity: 1;
transform: translateX(0) scale(1);
}
}
@keyframes glassToastSlideOut {
from {
opacity: 1;
transform: translateX(0) scale(1);
}
to {
opacity: 0;
transform: translateX(100px) scale(0.8);
}
}
`;
document.head.appendChild(style);
}
// 键盘快捷键支持
document.addEventListener('keydown', function(e) {
if (e.key === 'Enter') {
e.preventDefault();
queryLunarInfo();
}
if (e.key === 'r' && (e.ctrlKey || e.metaKey)) {
e.preventDefault();
queryLunarInfo();
}
});

View File

@@ -1,3 +1,3 @@
[
"https://60s.api.shumengya.top"
]
[
"https://60s.api.shumengya.top"
]

View File

@@ -1,212 +1,212 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>多功能哈希工具 - Hash Toolkit</title>
<link rel="stylesheet" href="css/style.css">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
</head>
<body>
<div class="container">
<!-- Header Section -->
<header class="header">
<div class="header-content">
<div class="logo">
<i class="fas fa-fingerprint"></i>
<h1>Hash Toolkit</h1>
</div>
<p class="subtitle">多功能哈希、编码与压缩工具</p>
</div>
<div class="header-decoration">
<div class="floating-shapes">
<div class="shape shape-1"></div>
<div class="shape shape-2"></div>
<div class="shape shape-3"></div>
</div>
</div>
</header>
<!-- Main Content -->
<main class="main-content">
<!-- Input Section -->
<section class="input-section">
<div class="input-card">
<div class="card-header">
<i class="fas fa-edit"></i>
<h2>输入内容</h2>
</div>
<div class="input-wrapper">
<textarea
id="inputText"
placeholder="请输入要处理的文本内容...\n支持中文、英文、特殊字符等"
rows="6"
></textarea>
<div class="input-actions">
<button id="clearBtn" class="btn btn-secondary">
<i class="fas fa-trash"></i>
清空
</button>
<button id="processBtn" class="btn btn-primary">
<i class="fas fa-cogs"></i>
开始处理
</button>
</div>
</div>
</div>
</section>
<!-- Results Section -->
<section class="results-section" id="resultsSection">
<div class="results-grid">
<!-- Hash Results -->
<div class="result-card hash-card">
<div class="card-header">
<i class="fas fa-hashtag"></i>
<h3>哈希算法</h3>
</div>
<div class="result-items">
<div class="result-item">
<label>MD5</label>
<div class="result-value" id="md5Result">
<span class="placeholder">等待处理...</span>
<button class="copy-btn" data-target="md5Result">
<i class="fas fa-copy"></i>
</button>
</div>
</div>
<div class="result-item">
<label>SHA1</label>
<div class="result-value" id="sha1Result">
<span class="placeholder">等待处理...</span>
<button class="copy-btn" data-target="sha1Result">
<i class="fas fa-copy"></i>
</button>
</div>
</div>
<div class="result-item">
<label>SHA256</label>
<div class="result-value" id="sha256Result">
<span class="placeholder">等待处理...</span>
<button class="copy-btn" data-target="sha256Result">
<i class="fas fa-copy"></i>
</button>
</div>
</div>
<div class="result-item">
<label>SHA512</label>
<div class="result-value" id="sha512Result">
<span class="placeholder">等待处理...</span>
<button class="copy-btn" data-target="sha512Result">
<i class="fas fa-copy"></i>
</button>
</div>
</div>
</div>
</div>
<!-- Encoding Results -->
<div class="result-card encoding-card">
<div class="card-header">
<i class="fas fa-code"></i>
<h3>编码转换</h3>
</div>
<div class="result-items">
<div class="result-item">
<label>Base64 编码</label>
<div class="result-value" id="base64EncodeResult">
<span class="placeholder">等待处理...</span>
<button class="copy-btn" data-target="base64EncodeResult">
<i class="fas fa-copy"></i>
</button>
</div>
</div>
<div class="result-item">
<label>Base64 解码</label>
<div class="result-value" id="base64DecodeResult">
<span class="placeholder">等待处理...</span>
<button class="copy-btn" data-target="base64DecodeResult">
<i class="fas fa-copy"></i>
</button>
</div>
</div>
<div class="result-item">
<label>URL 编码</label>
<div class="result-value" id="urlEncodeResult">
<span class="placeholder">等待处理...</span>
<button class="copy-btn" data-target="urlEncodeResult">
<i class="fas fa-copy"></i>
</button>
</div>
</div>
<div class="result-item">
<label>URL 解码</label>
<div class="result-value" id="urlDecodeResult">
<span class="placeholder">等待处理...</span>
<button class="copy-btn" data-target="urlDecodeResult">
<i class="fas fa-copy"></i>
</button>
</div>
</div>
</div>
</div>
<!-- Compression Results -->
<div class="result-card compression-card">
<div class="card-header">
<i class="fas fa-compress-alt"></i>
<h3>压缩算法</h3>
</div>
<div class="result-items">
<div class="result-item">
<label>Gzip 压缩</label>
<div class="result-value" id="gzipCompressResult">
<span class="placeholder">等待处理...</span>
<button class="copy-btn" data-target="gzipCompressResult">
<i class="fas fa-copy"></i>
</button>
</div>
</div>
<div class="result-item">
<label>Deflate 压缩</label>
<div class="result-value" id="deflateCompressResult">
<span class="placeholder">等待处理...</span>
<button class="copy-btn" data-target="deflateCompressResult">
<i class="fas fa-copy"></i>
</button>
</div>
</div>
<div class="result-item">
<label>Brotli 压缩</label>
<div class="result-value" id="brotliCompressResult">
<span class="placeholder">等待处理...</span>
<button class="copy-btn" data-target="brotliCompressResult">
<i class="fas fa-copy"></i>
</button>
</div>
</div>
</div>
</div>
</div>
</section>
</main>
<!-- Loading Overlay -->
<div class="loading-overlay" id="loadingOverlay">
<div class="loading-spinner">
<div class="spinner"></div>
<p>正在处理中...</p>
</div>
</div>
<!-- Toast Notification -->
<div class="toast" id="toast">
<i class="fas fa-check-circle"></i>
<span id="toastMessage">复制成功!</span>
</div>
</div>
<script src="js/script.js"></script>
</body>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>多功能哈希工具 - Hash Toolkit</title>
<link rel="stylesheet" href="css/style.css">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
</head>
<body>
<div class="container">
<!-- Header Section -->
<header class="header">
<div class="header-content">
<div class="logo">
<i class="fas fa-fingerprint"></i>
<h1>Hash Toolkit</h1>
</div>
<p class="subtitle">多功能哈希、编码与压缩工具</p>
</div>
<div class="header-decoration">
<div class="floating-shapes">
<div class="shape shape-1"></div>
<div class="shape shape-2"></div>
<div class="shape shape-3"></div>
</div>
</div>
</header>
<!-- Main Content -->
<main class="main-content">
<!-- Input Section -->
<section class="input-section">
<div class="input-card">
<div class="card-header">
<i class="fas fa-edit"></i>
<h2>输入内容</h2>
</div>
<div class="input-wrapper">
<textarea
id="inputText"
placeholder="请输入要处理的文本内容...\n支持中文、英文、特殊字符等"
rows="6"
></textarea>
<div class="input-actions">
<button id="clearBtn" class="btn btn-secondary">
<i class="fas fa-trash"></i>
清空
</button>
<button id="processBtn" class="btn btn-primary">
<i class="fas fa-cogs"></i>
开始处理
</button>
</div>
</div>
</div>
</section>
<!-- Results Section -->
<section class="results-section" id="resultsSection">
<div class="results-grid">
<!-- Hash Results -->
<div class="result-card hash-card">
<div class="card-header">
<i class="fas fa-hashtag"></i>
<h3>哈希算法</h3>
</div>
<div class="result-items">
<div class="result-item">
<label>MD5</label>
<div class="result-value" id="md5Result">
<span class="placeholder">等待处理...</span>
<button class="copy-btn" data-target="md5Result">
<i class="fas fa-copy"></i>
</button>
</div>
</div>
<div class="result-item">
<label>SHA1</label>
<div class="result-value" id="sha1Result">
<span class="placeholder">等待处理...</span>
<button class="copy-btn" data-target="sha1Result">
<i class="fas fa-copy"></i>
</button>
</div>
</div>
<div class="result-item">
<label>SHA256</label>
<div class="result-value" id="sha256Result">
<span class="placeholder">等待处理...</span>
<button class="copy-btn" data-target="sha256Result">
<i class="fas fa-copy"></i>
</button>
</div>
</div>
<div class="result-item">
<label>SHA512</label>
<div class="result-value" id="sha512Result">
<span class="placeholder">等待处理...</span>
<button class="copy-btn" data-target="sha512Result">
<i class="fas fa-copy"></i>
</button>
</div>
</div>
</div>
</div>
<!-- Encoding Results -->
<div class="result-card encoding-card">
<div class="card-header">
<i class="fas fa-code"></i>
<h3>编码转换</h3>
</div>
<div class="result-items">
<div class="result-item">
<label>Base64 编码</label>
<div class="result-value" id="base64EncodeResult">
<span class="placeholder">等待处理...</span>
<button class="copy-btn" data-target="base64EncodeResult">
<i class="fas fa-copy"></i>
</button>
</div>
</div>
<div class="result-item">
<label>Base64 解码</label>
<div class="result-value" id="base64DecodeResult">
<span class="placeholder">等待处理...</span>
<button class="copy-btn" data-target="base64DecodeResult">
<i class="fas fa-copy"></i>
</button>
</div>
</div>
<div class="result-item">
<label>URL 编码</label>
<div class="result-value" id="urlEncodeResult">
<span class="placeholder">等待处理...</span>
<button class="copy-btn" data-target="urlEncodeResult">
<i class="fas fa-copy"></i>
</button>
</div>
</div>
<div class="result-item">
<label>URL 解码</label>
<div class="result-value" id="urlDecodeResult">
<span class="placeholder">等待处理...</span>
<button class="copy-btn" data-target="urlDecodeResult">
<i class="fas fa-copy"></i>
</button>
</div>
</div>
</div>
</div>
<!-- Compression Results -->
<div class="result-card compression-card">
<div class="card-header">
<i class="fas fa-compress-alt"></i>
<h3>压缩算法</h3>
</div>
<div class="result-items">
<div class="result-item">
<label>Gzip 压缩</label>
<div class="result-value" id="gzipCompressResult">
<span class="placeholder">等待处理...</span>
<button class="copy-btn" data-target="gzipCompressResult">
<i class="fas fa-copy"></i>
</button>
</div>
</div>
<div class="result-item">
<label>Deflate 压缩</label>
<div class="result-value" id="deflateCompressResult">
<span class="placeholder">等待处理...</span>
<button class="copy-btn" data-target="deflateCompressResult">
<i class="fas fa-copy"></i>
</button>
</div>
</div>
<div class="result-item">
<label>Brotli 压缩</label>
<div class="result-value" id="brotliCompressResult">
<span class="placeholder">等待处理...</span>
<button class="copy-btn" data-target="brotliCompressResult">
<i class="fas fa-copy"></i>
</button>
</div>
</div>
</div>
</div>
</div>
</section>
</main>
<!-- Loading Overlay -->
<div class="loading-overlay" id="loadingOverlay">
<div class="loading-spinner">
<div class="spinner"></div>
<p>正在处理中...</p>
</div>
</div>
<!-- Toast Notification -->
<div class="toast" id="toast">
<i class="fas fa-check-circle"></i>
<span id="toastMessage">复制成功!</span>
</div>
</div>
<script src="js/script.js"></script>
</body>
</html>

View File

@@ -1,394 +1,394 @@
// API配置
const API_BASE_URL = 'https://60s.api.shumengya.top/v2/hash';
// DOM元素
const elements = {
inputText: document.getElementById('inputText'),
processBtn: document.getElementById('processBtn'),
clearBtn: document.getElementById('clearBtn'),
resultsSection: document.getElementById('resultsSection'),
loadingOverlay: document.getElementById('loadingOverlay'),
toast: document.getElementById('toast'),
toastMessage: document.getElementById('toastMessage')
};
// 结果元素映射
const resultElements = {
md5: document.getElementById('md5Result'),
sha1: document.getElementById('sha1Result'),
sha256: document.getElementById('sha256Result'),
sha512: document.getElementById('sha512Result'),
base64Encode: document.getElementById('base64EncodeResult'),
base64Decode: document.getElementById('base64DecodeResult'),
urlEncode: document.getElementById('urlEncodeResult'),
urlDecode: document.getElementById('urlDecodeResult'),
gzipCompress: document.getElementById('gzipCompressResult'),
deflateCompress: document.getElementById('deflateCompressResult'),
brotliCompress: document.getElementById('brotliCompressResult')
};
// 初始化
document.addEventListener('DOMContentLoaded', function() {
initializeEventListeners();
addInputAnimation();
});
// 事件监听器初始化
function initializeEventListeners() {
// 处理按钮点击
elements.processBtn.addEventListener('click', handleProcess);
// 清空按钮点击
elements.clearBtn.addEventListener('click', handleClear);
// 输入框回车键
elements.inputText.addEventListener('keydown', function(e) {
if (e.ctrlKey && e.key === 'Enter') {
handleProcess();
}
});
// 复制按钮事件委托
document.addEventListener('click', function(e) {
if (e.target.closest('.copy-btn')) {
const copyBtn = e.target.closest('.copy-btn');
const targetId = copyBtn.getAttribute('data-target');
const targetElement = document.getElementById(targetId);
const textContent = targetElement.textContent.trim();
if (textContent && textContent !== '等待处理...' && textContent !== '处理失败') {
copyToClipboard(textContent);
}
}
});
// 输入框实时验证
elements.inputText.addEventListener('input', function() {
const hasContent = this.value.trim().length > 0;
elements.processBtn.disabled = !hasContent;
if (hasContent) {
elements.processBtn.classList.remove('disabled');
} else {
elements.processBtn.classList.add('disabled');
}
});
}
// 添加输入动画效果
function addInputAnimation() {
elements.inputText.addEventListener('focus', function() {
this.parentElement.classList.add('focused');
});
elements.inputText.addEventListener('blur', function() {
this.parentElement.classList.remove('focused');
});
}
// 处理主要功能
async function handleProcess() {
const inputValue = elements.inputText.value.trim();
if (!inputValue) {
showToast('请输入要处理的内容', 'error');
return;
}
// 显示加载状态
showLoading(true);
resetResults();
try {
// 调用API
const response = await fetch(`${API_BASE_URL}?content=${encodeURIComponent(inputValue)}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
if (data.code === 200 && data.data) {
displayResults(data.data);
showResultsSection();
showToast('处理完成!', 'success');
} else {
throw new Error(data.message || '处理失败');
}
} catch (error) {
console.error('处理错误:', error);
showToast(`处理失败: ${error.message}`, 'error');
displayError();
} finally {
showLoading(false);
}
}
// 显示结果
function displayResults(data) {
try {
// 哈希结果
updateResultElement('md5', data.md5 || '不可用');
// SHA系列
if (data.sha) {
updateResultElement('sha1', data.sha.sha1 || '不可用');
updateResultElement('sha256', data.sha.sha256 || '不可用');
updateResultElement('sha512', data.sha.sha512 || '不可用');
}
// Base64编码
if (data.base64) {
updateResultElement('base64Encode', data.base64.encoded || '不可用');
// BASE64解码只有当输入本身是BASE64格式时才显示解码结果
let base64DecodeResult = data.base64.decoded;
if (!base64DecodeResult) {
// 检查输入是否为有效的BASE64格式
const inputValue = elements.inputText.value.trim();
const base64Regex = /^[A-Za-z0-9+/]*={0,2}$/;
if (base64Regex.test(inputValue) && inputValue.length % 4 === 0) {
try {
base64DecodeResult = atob(inputValue);
} catch (e) {
base64DecodeResult = '解码失败';
}
} else {
base64DecodeResult = '输入非BASE64格式';
}
}
updateResultElement('base64Decode', base64DecodeResult || '不可用');
}
// URL编码
if (data.url) {
updateResultElement('urlEncode', data.url.encoded || '不可用');
updateResultElement('urlDecode', data.url.decoded || '不可用');
}
// 压缩结果(仅显示压缩,不显示解压)
if (data.gzip) {
updateResultElement('gzipCompress', data.gzip.encoded || '不可用');
}
if (data.deflate) {
updateResultElement('deflateCompress', data.deflate.encoded || '不可用');
}
if (data.brotli) {
updateResultElement('brotliCompress', data.brotli.encoded || '不可用');
}
} catch (error) {
console.error('显示结果时出错:', error);
showToast('显示结果时出错', 'error');
}
}
// 更新单个结果元素
function updateResultElement(key, value) {
const element = resultElements[key];
if (element) {
const textSpan = element.querySelector('span') || element;
textSpan.textContent = value;
textSpan.classList.remove('placeholder');
// 添加动画效果
element.classList.add('slide-in');
setTimeout(() => {
element.classList.remove('slide-in');
}, 300);
}
}
// 重置结果
function resetResults() {
Object.values(resultElements).forEach(element => {
if (element) {
const textSpan = element.querySelector('span') || element;
textSpan.textContent = '等待处理...';
textSpan.classList.add('placeholder');
}
});
}
// 显示错误状态
function displayError() {
Object.values(resultElements).forEach(element => {
if (element) {
const textSpan = element.querySelector('span') || element;
textSpan.textContent = '处理失败';
textSpan.classList.add('placeholder');
}
});
}
// 显示结果区域
function showResultsSection() {
elements.resultsSection.classList.add('show');
// 平滑滚动到结果区域
setTimeout(() => {
elements.resultsSection.scrollIntoView({
behavior: 'smooth',
block: 'start'
});
}, 100);
}
// 清空功能
function handleClear() {
elements.inputText.value = '';
elements.inputText.focus();
elements.resultsSection.classList.remove('show');
resetResults();
elements.processBtn.disabled = true;
elements.processBtn.classList.add('disabled');
showToast('内容已清空', 'info');
}
// 复制到剪贴板
async function copyToClipboard(text) {
try {
if (navigator.clipboard && window.isSecureContext) {
await navigator.clipboard.writeText(text);
} else {
// 降级方案
const textArea = document.createElement('textarea');
textArea.value = text;
textArea.style.position = 'fixed';
textArea.style.left = '-999999px';
textArea.style.top = '-999999px';
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
document.execCommand('copy');
textArea.remove();
}
showToast('复制成功!', 'success');
} catch (error) {
console.error('复制失败:', error);
showToast('复制失败,请手动复制', 'error');
}
}
// 显示/隐藏加载状态
function showLoading(show) {
if (show) {
elements.loadingOverlay.classList.add('show');
elements.processBtn.disabled = true;
elements.processBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> 处理中...';
} else {
elements.loadingOverlay.classList.remove('show');
elements.processBtn.disabled = false;
elements.processBtn.innerHTML = '<i class="fas fa-cogs"></i> 开始处理';
}
}
// 显示提示消息
function showToast(message, type = 'success') {
elements.toastMessage.textContent = message;
// 设置图标和样式
const icon = elements.toast.querySelector('i');
icon.className = getToastIcon(type);
elements.toast.className = `toast ${type}`;
elements.toast.classList.add('show');
// 自动隐藏
setTimeout(() => {
elements.toast.classList.remove('show');
}, 3000);
}
// 获取提示图标
function getToastIcon(type) {
const icons = {
success: 'fas fa-check-circle',
error: 'fas fa-exclamation-circle',
info: 'fas fa-info-circle',
warning: 'fas fa-exclamation-triangle'
};
return icons[type] || icons.success;
}
// 工具函数:防抖
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// 工具函数:节流
function throttle(func, limit) {
let inThrottle;
return function() {
const args = arguments;
const context = this;
if (!inThrottle) {
func.apply(context, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
}
}
// 添加键盘快捷键支持
document.addEventListener('keydown', function(e) {
// Ctrl+Enter 处理
if (e.ctrlKey && e.key === 'Enter') {
e.preventDefault();
if (!elements.processBtn.disabled) {
handleProcess();
}
}
// Escape 清空
if (e.key === 'Escape') {
handleClear();
}
});
// 页面可见性变化处理
document.addEventListener('visibilitychange', function() {
if (document.hidden) {
// 页面隐藏时的处理
console.log('页面已隐藏');
} else {
// 页面显示时的处理
console.log('页面已显示');
}
});
// 错误处理
window.addEventListener('error', function(e) {
console.error('全局错误:', e.error);
showToast('发生未知错误,请刷新页面重试', 'error');
});
// 未处理的Promise拒绝
window.addEventListener('unhandledrejection', function(e) {
console.error('未处理的Promise拒绝:', e.reason);
showToast('网络请求失败,请检查网络连接', 'error');
});
// 导出函数供测试使用
if (typeof module !== 'undefined' && module.exports) {
module.exports = {
handleProcess,
copyToClipboard,
showToast,
debounce,
throttle
};
// API配置
const API_BASE_URL = 'https://60s.api.shumengya.top/v2/hash';
// DOM元素
const elements = {
inputText: document.getElementById('inputText'),
processBtn: document.getElementById('processBtn'),
clearBtn: document.getElementById('clearBtn'),
resultsSection: document.getElementById('resultsSection'),
loadingOverlay: document.getElementById('loadingOverlay'),
toast: document.getElementById('toast'),
toastMessage: document.getElementById('toastMessage')
};
// 结果元素映射
const resultElements = {
md5: document.getElementById('md5Result'),
sha1: document.getElementById('sha1Result'),
sha256: document.getElementById('sha256Result'),
sha512: document.getElementById('sha512Result'),
base64Encode: document.getElementById('base64EncodeResult'),
base64Decode: document.getElementById('base64DecodeResult'),
urlEncode: document.getElementById('urlEncodeResult'),
urlDecode: document.getElementById('urlDecodeResult'),
gzipCompress: document.getElementById('gzipCompressResult'),
deflateCompress: document.getElementById('deflateCompressResult'),
brotliCompress: document.getElementById('brotliCompressResult')
};
// 初始化
document.addEventListener('DOMContentLoaded', function() {
initializeEventListeners();
addInputAnimation();
});
// 事件监听器初始化
function initializeEventListeners() {
// 处理按钮点击
elements.processBtn.addEventListener('click', handleProcess);
// 清空按钮点击
elements.clearBtn.addEventListener('click', handleClear);
// 输入框回车键
elements.inputText.addEventListener('keydown', function(e) {
if (e.ctrlKey && e.key === 'Enter') {
handleProcess();
}
});
// 复制按钮事件委托
document.addEventListener('click', function(e) {
if (e.target.closest('.copy-btn')) {
const copyBtn = e.target.closest('.copy-btn');
const targetId = copyBtn.getAttribute('data-target');
const targetElement = document.getElementById(targetId);
const textContent = targetElement.textContent.trim();
if (textContent && textContent !== '等待处理...' && textContent !== '处理失败') {
copyToClipboard(textContent);
}
}
});
// 输入框实时验证
elements.inputText.addEventListener('input', function() {
const hasContent = this.value.trim().length > 0;
elements.processBtn.disabled = !hasContent;
if (hasContent) {
elements.processBtn.classList.remove('disabled');
} else {
elements.processBtn.classList.add('disabled');
}
});
}
// 添加输入动画效果
function addInputAnimation() {
elements.inputText.addEventListener('focus', function() {
this.parentElement.classList.add('focused');
});
elements.inputText.addEventListener('blur', function() {
this.parentElement.classList.remove('focused');
});
}
// 处理主要功能
async function handleProcess() {
const inputValue = elements.inputText.value.trim();
if (!inputValue) {
showToast('请输入要处理的内容', 'error');
return;
}
// 显示加载状态
showLoading(true);
resetResults();
try {
// 调用API
const response = await fetch(`${API_BASE_URL}?content=${encodeURIComponent(inputValue)}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
if (data.code === 200 && data.data) {
displayResults(data.data);
showResultsSection();
showToast('处理完成!', 'success');
} else {
throw new Error(data.message || '处理失败');
}
} catch (error) {
console.error('处理错误:', error);
showToast(`处理失败: ${error.message}`, 'error');
displayError();
} finally {
showLoading(false);
}
}
// 显示结果
function displayResults(data) {
try {
// 哈希结果
updateResultElement('md5', data.md5 || '不可用');
// SHA系列
if (data.sha) {
updateResultElement('sha1', data.sha.sha1 || '不可用');
updateResultElement('sha256', data.sha.sha256 || '不可用');
updateResultElement('sha512', data.sha.sha512 || '不可用');
}
// Base64编码
if (data.base64) {
updateResultElement('base64Encode', data.base64.encoded || '不可用');
// BASE64解码只有当输入本身是BASE64格式时才显示解码结果
let base64DecodeResult = data.base64.decoded;
if (!base64DecodeResult) {
// 检查输入是否为有效的BASE64格式
const inputValue = elements.inputText.value.trim();
const base64Regex = /^[A-Za-z0-9+/]*={0,2}$/;
if (base64Regex.test(inputValue) && inputValue.length % 4 === 0) {
try {
base64DecodeResult = atob(inputValue);
} catch (e) {
base64DecodeResult = '解码失败';
}
} else {
base64DecodeResult = '输入非BASE64格式';
}
}
updateResultElement('base64Decode', base64DecodeResult || '不可用');
}
// URL编码
if (data.url) {
updateResultElement('urlEncode', data.url.encoded || '不可用');
updateResultElement('urlDecode', data.url.decoded || '不可用');
}
// 压缩结果(仅显示压缩,不显示解压)
if (data.gzip) {
updateResultElement('gzipCompress', data.gzip.encoded || '不可用');
}
if (data.deflate) {
updateResultElement('deflateCompress', data.deflate.encoded || '不可用');
}
if (data.brotli) {
updateResultElement('brotliCompress', data.brotli.encoded || '不可用');
}
} catch (error) {
console.error('显示结果时出错:', error);
showToast('显示结果时出错', 'error');
}
}
// 更新单个结果元素
function updateResultElement(key, value) {
const element = resultElements[key];
if (element) {
const textSpan = element.querySelector('span') || element;
textSpan.textContent = value;
textSpan.classList.remove('placeholder');
// 添加动画效果
element.classList.add('slide-in');
setTimeout(() => {
element.classList.remove('slide-in');
}, 300);
}
}
// 重置结果
function resetResults() {
Object.values(resultElements).forEach(element => {
if (element) {
const textSpan = element.querySelector('span') || element;
textSpan.textContent = '等待处理...';
textSpan.classList.add('placeholder');
}
});
}
// 显示错误状态
function displayError() {
Object.values(resultElements).forEach(element => {
if (element) {
const textSpan = element.querySelector('span') || element;
textSpan.textContent = '处理失败';
textSpan.classList.add('placeholder');
}
});
}
// 显示结果区域
function showResultsSection() {
elements.resultsSection.classList.add('show');
// 平滑滚动到结果区域
setTimeout(() => {
elements.resultsSection.scrollIntoView({
behavior: 'smooth',
block: 'start'
});
}, 100);
}
// 清空功能
function handleClear() {
elements.inputText.value = '';
elements.inputText.focus();
elements.resultsSection.classList.remove('show');
resetResults();
elements.processBtn.disabled = true;
elements.processBtn.classList.add('disabled');
showToast('内容已清空', 'info');
}
// 复制到剪贴板
async function copyToClipboard(text) {
try {
if (navigator.clipboard && window.isSecureContext) {
await navigator.clipboard.writeText(text);
} else {
// 降级方案
const textArea = document.createElement('textarea');
textArea.value = text;
textArea.style.position = 'fixed';
textArea.style.left = '-999999px';
textArea.style.top = '-999999px';
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
document.execCommand('copy');
textArea.remove();
}
showToast('复制成功!', 'success');
} catch (error) {
console.error('复制失败:', error);
showToast('复制失败,请手动复制', 'error');
}
}
// 显示/隐藏加载状态
function showLoading(show) {
if (show) {
elements.loadingOverlay.classList.add('show');
elements.processBtn.disabled = true;
elements.processBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> 处理中...';
} else {
elements.loadingOverlay.classList.remove('show');
elements.processBtn.disabled = false;
elements.processBtn.innerHTML = '<i class="fas fa-cogs"></i> 开始处理';
}
}
// 显示提示消息
function showToast(message, type = 'success') {
elements.toastMessage.textContent = message;
// 设置图标和样式
const icon = elements.toast.querySelector('i');
icon.className = getToastIcon(type);
elements.toast.className = `toast ${type}`;
elements.toast.classList.add('show');
// 自动隐藏
setTimeout(() => {
elements.toast.classList.remove('show');
}, 3000);
}
// 获取提示图标
function getToastIcon(type) {
const icons = {
success: 'fas fa-check-circle',
error: 'fas fa-exclamation-circle',
info: 'fas fa-info-circle',
warning: 'fas fa-exclamation-triangle'
};
return icons[type] || icons.success;
}
// 工具函数:防抖
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// 工具函数:节流
function throttle(func, limit) {
let inThrottle;
return function() {
const args = arguments;
const context = this;
if (!inThrottle) {
func.apply(context, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
}
}
// 添加键盘快捷键支持
document.addEventListener('keydown', function(e) {
// Ctrl+Enter 处理
if (e.ctrlKey && e.key === 'Enter') {
e.preventDefault();
if (!elements.processBtn.disabled) {
handleProcess();
}
}
// Escape 清空
if (e.key === 'Escape') {
handleClear();
}
});
// 页面可见性变化处理
document.addEventListener('visibilitychange', function() {
if (document.hidden) {
// 页面隐藏时的处理
console.log('页面已隐藏');
} else {
// 页面显示时的处理
console.log('页面已显示');
}
});
// 错误处理
window.addEventListener('error', function(e) {
console.error('全局错误:', e.error);
showToast('发生未知错误,请刷新页面重试', 'error');
});
// 未处理的Promise拒绝
window.addEventListener('unhandledrejection', function(e) {
console.error('未处理的Promise拒绝:', e.reason);
showToast('网络请求失败,请检查网络连接', 'error');
});
// 导出函数供测试使用
if (typeof module !== 'undefined' && module.exports) {
module.exports = {
handleProcess,
copyToClipboard,
showToast,
debounce,
throttle
};
}

View File

@@ -1,3 +1,3 @@
[
"https://60s.api.shumengya.top"
]
[
"https://60s.api.shumengya.top"
]

View File

@@ -1,35 +1,35 @@
{
"code": 200,
"message": "获取成功。数据来自官方/权威源头,以确保稳定与实时。开源地址 https://github.com/vikiboss/60s反馈群 595941841",
"data": {
"source": "hello",
"md5": "5d41402abc4b2a76b9719d911017c592",
"sha": {
"sha1": "aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d",
"sha256": "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824",
"sha512": "9b71d224bd62f3785d96d46ad3ea3d73319bfbc2890caadae2dff72519673ca72323c3d99ba5c11d7c7acc6e14b8c5da0c4663475c2e5c3adef46f73bcdec043"
},
"base64": {
"encoded": "aGVsbG8=",
"decoded": ""
},
"url": {
"encoded": "hello",
"decoded": "hello"
},
"gzip": {
"encoded": "1f8b0800000000000003cb48cdc9c9070086a6103605000000",
"decoded": ""
},
"deflate": {
"encoded": "789ccb48cdc9c90700062c0215",
"decoded": ""
},
"brotli": {
"encoded": "0b028068656c6c6f03",
"decoded": ""
}
}
}
{
"code": 200,
"message": "获取成功。数据来自官方/权威源头,以确保稳定与实时。开源地址 https://github.com/vikiboss/60s反馈群 595941841",
"data": {
"source": "hello",
"md5": "5d41402abc4b2a76b9719d911017c592",
"sha": {
"sha1": "aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d",
"sha256": "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824",
"sha512": "9b71d224bd62f3785d96d46ad3ea3d73319bfbc2890caadae2dff72519673ca72323c3d99ba5c11d7c7acc6e14b8c5da0c4663475c2e5c3adef46f73bcdec043"
},
"base64": {
"encoded": "aGVsbG8=",
"decoded": ""
},
"url": {
"encoded": "hello",
"decoded": "hello"
},
"gzip": {
"encoded": "1f8b0800000000000003cb48cdc9c9070086a6103605000000",
"decoded": ""
},
"deflate": {
"encoded": "789ccb48cdc9c90700062c0215",
"decoded": ""
},
"brotli": {
"encoded": "0b028068656c6c6f03",
"decoded": ""
}
}
}
API encoded/decoded encode/decode

View File

@@ -1,137 +1,137 @@
/* 背景样式文件 */
/* 页面主背景 */
body {
background: linear-gradient(135deg, #e8f5e8 0%, #f0fdf4 25%, #dcfce7 50%, #f0fdf4 75%, #e8f5e8 100%);
background-size: 400% 400%;
animation: gradientShift 15s ease infinite;
background-attachment: fixed;
}
/* 背景动画 */
@keyframes gradientShift {
0% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
}
/* 容器背景 */
.container {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 20px;
border: 1px solid rgba(255, 255, 255, 0.2);
}
/* 翻译框背景 */
.translate-box {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20px);
border: 1px solid rgba(116, 198, 157, 0.3);
}
/* 输入框背景 */
#input-text {
background: rgba(255, 255, 255, 0.9);
}
#input-text:focus {
background: rgba(255, 255, 255, 1);
}
/* 输出框背景 */
.output-text {
background: #f8fffe;
}
/* 按钮背景 */
.translate-btn {
background: linear-gradient(135deg, #74c69d, #52b788);
}
.translate-btn:hover {
background: linear-gradient(135deg, #52b788, #40916c);
}
.translate-btn:disabled {
background: #b7e4c7;
}
.swap-btn {
background: #74c69d;
}
.swap-btn:hover {
background: #52b788;
}
/* 语言选择器背景 */
.lang-select {
background: white;
}
.lang-select:focus {
background: rgba(255, 255, 255, 1);
}
/* 发音信息背景 */
.pronounce-item {
background: rgba(116, 198, 157, 0.1);
}
/* 清除和复制按钮背景 */
.clear-btn:hover,
.copy-btn:hover {
background: rgba(116, 198, 157, 0.1);
}
/* 提示消息背景 */
.toast {
background: #52b788;
}
.toast.error {
background: #e74c3c;
}
/* 响应式背景调整 */
@media (max-width: 767px) {
body {
background-size: 200% 200%;
animation-duration: 10s;
}
.container {
background: rgba(255, 255, 255, 0.05);
backdrop-filter: blur(5px);
}
.translate-box {
background: rgba(255, 255, 255, 0.98);
backdrop-filter: blur(15px);
}
}
@media (max-width: 480px) {
body {
background-size: 150% 150%;
animation-duration: 8s;
}
.container {
background: transparent;
backdrop-filter: none;
border: none;
}
.translate-box {
background: rgba(255, 255, 255, 0.99);
backdrop-filter: blur(10px);
}
/* 背景样式文件 */
/* 页面主背景 */
body {
background: linear-gradient(135deg, #e8f5e8 0%, #f0fdf4 25%, #dcfce7 50%, #f0fdf4 75%, #e8f5e8 100%);
background-size: 400% 400%;
animation: gradientShift 15s ease infinite;
background-attachment: fixed;
}
/* 背景动画 */
@keyframes gradientShift {
0% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
}
/* 容器背景 */
.container {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 20px;
border: 1px solid rgba(255, 255, 255, 0.2);
}
/* 翻译框背景 */
.translate-box {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20px);
border: 1px solid rgba(116, 198, 157, 0.3);
}
/* 输入框背景 */
#input-text {
background: rgba(255, 255, 255, 0.9);
}
#input-text:focus {
background: rgba(255, 255, 255, 1);
}
/* 输出框背景 */
.output-text {
background: #f8fffe;
}
/* 按钮背景 */
.translate-btn {
background: linear-gradient(135deg, #74c69d, #52b788);
}
.translate-btn:hover {
background: linear-gradient(135deg, #52b788, #40916c);
}
.translate-btn:disabled {
background: #b7e4c7;
}
.swap-btn {
background: #74c69d;
}
.swap-btn:hover {
background: #52b788;
}
/* 语言选择器背景 */
.lang-select {
background: white;
}
.lang-select:focus {
background: rgba(255, 255, 255, 1);
}
/* 发音信息背景 */
.pronounce-item {
background: rgba(116, 198, 157, 0.1);
}
/* 清除和复制按钮背景 */
.clear-btn:hover,
.copy-btn:hover {
background: rgba(116, 198, 157, 0.1);
}
/* 提示消息背景 */
.toast {
background: #52b788;
}
.toast.error {
background: #e74c3c;
}
/* 响应式背景调整 */
@media (max-width: 767px) {
body {
background-size: 200% 200%;
animation-duration: 10s;
}
.container {
background: rgba(255, 255, 255, 0.05);
backdrop-filter: blur(5px);
}
.translate-box {
background: rgba(255, 255, 255, 0.98);
backdrop-filter: blur(15px);
}
}
@media (max-width: 480px) {
body {
background-size: 150% 150%;
animation-duration: 8s;
}
.container {
background: transparent;
backdrop-filter: none;
border: none;
}
.translate-box {
background: rgba(255, 255, 255, 0.99);
backdrop-filter: blur(10px);
}
}

View File

@@ -1,99 +1,99 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>在线翻译 - 支持109种语言</title>
<link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="background.css">
</head>
<body>
<div class="container">
<header class="header">
<h1>在线机器翻译</h1>
<p class="subtitle">支持109种语言互译</p>
</header>
<main class="main-content">
<div class="translate-box">
<div class="language-selector">
<div class="lang-group">
<label for="from-lang">源语言</label>
<select id="from-lang" class="lang-select">
<option value="auto">自动检测</option>
</select>
</div>
<button class="swap-btn" id="swap-btn" title="交换语言">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M7 16l4-4-4-4"></path>
<path d="M17 8l-4 4 4 4"></path>
</svg>
</button>
<div class="lang-group">
<label for="to-lang">目标语言</label>
<select id="to-lang" class="lang-select">
<option value="auto">自动选择</option>
</select>
</div>
</div>
<div class="text-areas">
<div class="input-section">
<div class="textarea-header">
<span class="detected-lang" id="detected-lang"></span>
<button class="clear-btn" id="clear-btn" title="清空">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
</div>
<textarea
id="input-text"
placeholder="请输入要翻译的文本..."
maxlength="5000"
></textarea>
<div class="char-count">
<span id="char-count">0</span>/5000
</div>
</div>
<div class="output-section">
<div class="textarea-header">
<span class="target-lang" id="target-lang"></span>
<button class="copy-btn" id="copy-btn" title="复制">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
</svg>
</button>
</div>
<div id="output-text" class="output-text">翻译结果将显示在这里...</div>
<div class="pronounce-section" id="pronounce-section">
<div class="pronounce-item" id="source-pronounce"></div>
<div class="pronounce-item" id="target-pronounce"></div>
</div>
</div>
</div>
<div class="action-buttons">
<button class="translate-btn" id="translate-btn">
<span class="btn-text">翻译</span>
<div class="loading-spinner" id="loading-spinner"></div>
</button>
</div>
</div>
</main>
<footer class="footer">
<p>数据来源于有道翻译,与其网页端同步</p>
</footer>
</div>
<div class="toast" id="toast"></div>
<script src="script.js"></script>
</body>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>在线翻译 - 支持109种语言</title>
<link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="background.css">
</head>
<body>
<div class="container">
<header class="header">
<h1>在线机器翻译</h1>
<p class="subtitle">支持109种语言互译</p>
</header>
<main class="main-content">
<div class="translate-box">
<div class="language-selector">
<div class="lang-group">
<label for="from-lang">源语言</label>
<select id="from-lang" class="lang-select">
<option value="auto">自动检测</option>
</select>
</div>
<button class="swap-btn" id="swap-btn" title="交换语言">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M7 16l4-4-4-4"></path>
<path d="M17 8l-4 4 4 4"></path>
</svg>
</button>
<div class="lang-group">
<label for="to-lang">目标语言</label>
<select id="to-lang" class="lang-select">
<option value="auto">自动选择</option>
</select>
</div>
</div>
<div class="text-areas">
<div class="input-section">
<div class="textarea-header">
<span class="detected-lang" id="detected-lang"></span>
<button class="clear-btn" id="clear-btn" title="清空">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
</div>
<textarea
id="input-text"
placeholder="请输入要翻译的文本..."
maxlength="5000"
></textarea>
<div class="char-count">
<span id="char-count">0</span>/5000
</div>
</div>
<div class="output-section">
<div class="textarea-header">
<span class="target-lang" id="target-lang"></span>
<button class="copy-btn" id="copy-btn" title="复制">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
</svg>
</button>
</div>
<div id="output-text" class="output-text">翻译结果将显示在这里...</div>
<div class="pronounce-section" id="pronounce-section">
<div class="pronounce-item" id="source-pronounce"></div>
<div class="pronounce-item" id="target-pronounce"></div>
</div>
</div>
</div>
<div class="action-buttons">
<button class="translate-btn" id="translate-btn">
<span class="btn-text">翻译</span>
<div class="loading-spinner" id="loading-spinner"></div>
</button>
</div>
</div>
</main>
<footer class="footer">
<p>数据来源于有道翻译,与其网页端同步</p>
</footer>
</div>
<div class="toast" id="toast"></div>
<script src="script.js"></script>
</body>
</html>

View File

@@ -1,452 +1,452 @@
// 全局变量
let supportedLanguages = {};
let isTranslating = false;
// DOM元素
const elements = {
fromLang: null,
toLang: null,
inputText: null,
outputText: null,
translateBtn: null,
swapBtn: null,
clearBtn: null,
copyBtn: null,
charCount: null,
detectedLang: null,
targetLang: null,
pronounceSection: null
};
// 初始化
document.addEventListener('DOMContentLoaded', function() {
initializeElements();
loadSupportedLanguages();
bindEvents();
updateCharCount();
});
// 初始化DOM元素
function initializeElements() {
elements.fromLang = document.getElementById('from-lang');
elements.toLang = document.getElementById('to-lang');
elements.inputText = document.getElementById('input-text');
elements.outputText = document.getElementById('output-text');
elements.translateBtn = document.getElementById('translate-btn');
elements.swapBtn = document.getElementById('swap-btn');
elements.clearBtn = document.getElementById('clear-btn');
elements.copyBtn = document.getElementById('copy-btn');
elements.charCount = document.getElementById('char-count');
elements.detectedLang = document.getElementById('detected-lang');
elements.targetLang = document.getElementById('target-lang');
elements.pronounceSection = document.getElementById('pronounce-section');
}
// 加载支持的语言列表
async function loadSupportedLanguages() {
try {
const response = await fetch('https://60s.viki.moe/v2/fanyi/langs');
const data = await response.json();
if (data.code === 200 && data.data && Array.isArray(data.data)) {
// 转换数组格式为对象格式
supportedLanguages = {};
supportedLanguages['auto'] = '自动检测';
data.data.forEach(lang => {
supportedLanguages[lang.code] = lang.label;
});
populateLanguageSelectors();
} else {
throw new Error('获取语言列表失败');
}
} catch (error) {
console.error('加载语言列表失败:', error);
showToast('加载语言列表失败,请刷新页面重试', 'error');
// 使用默认语言列表
useDefaultLanguages();
}
}
// 使用默认语言列表(备用方案)
function useDefaultLanguages() {
supportedLanguages = {
'auto': '自动检测',
'zh-CHS': '中文',
'en': '英语',
'ja': '日语',
'ko': '韩语',
'fr': '法语',
'de': '德语',
'es': '西班牙语',
'ru': '俄语',
'th': '泰语',
'ar': '阿拉伯语',
'pt': '葡萄牙语',
'it': '意大利语'
};
populateLanguageSelectors();
}
// 填充语言选择器
function populateLanguageSelectors() {
const fromSelect = elements.fromLang;
const toSelect = elements.toLang;
// 清空现有选项
fromSelect.innerHTML = '';
toSelect.innerHTML = '';
// 添加语言选项
Object.entries(supportedLanguages).forEach(([code, name]) => {
const fromOption = new Option(name, code);
const toOption = new Option(name, code);
fromSelect.appendChild(fromOption);
toSelect.appendChild(toOption);
});
// 设置默认值
fromSelect.value = 'auto';
toSelect.value = 'en';
// 如果没有auto选项则设置为中文
if (!supportedLanguages['auto']) {
fromSelect.value = 'zh-CHS';
}
}
// 绑定事件
function bindEvents() {
// 输入框事件
elements.inputText.addEventListener('input', function() {
updateCharCount();
clearOutput();
});
elements.inputText.addEventListener('keydown', function(e) {
if (e.ctrlKey && e.key === 'Enter') {
translateText();
}
});
// 按钮事件
elements.translateBtn.addEventListener('click', translateText);
elements.swapBtn.addEventListener('click', swapLanguages);
elements.clearBtn.addEventListener('click', clearInput);
elements.copyBtn.addEventListener('click', copyOutput);
// 语言选择器事件
elements.fromLang.addEventListener('change', function() {
clearOutput();
updateLanguageLabels();
});
elements.toLang.addEventListener('change', function() {
clearOutput();
updateLanguageLabels();
});
}
// 更新字符计数
function updateCharCount() {
const text = elements.inputText.value;
const count = text.length;
elements.charCount.textContent = `${count}/5000`;
if (count > 5000) {
elements.charCount.style.color = '#e74c3c';
} else {
elements.charCount.style.color = '#74c69d';
}
}
// 更新语言标签
function updateLanguageLabels() {
const fromLang = elements.fromLang.value;
const toLang = elements.toLang.value;
elements.detectedLang.textContent = supportedLanguages[fromLang] || '未知语言';
elements.targetLang.textContent = supportedLanguages[toLang] || '未知语言';
}
// 翻译文本
async function translateText() {
const text = elements.inputText.value.trim();
if (!text) {
showToast('请输入要翻译的文本', 'error');
return;
}
if (text.length > 5000) {
showToast('文本长度不能超过5000字符', 'error');
return;
}
if (isTranslating) {
return;
}
setTranslating(true);
try {
const fromLang = elements.fromLang.value;
const toLang = elements.toLang.value;
// 构建请求URL
const params = new URLSearchParams({
text: text,
from: fromLang,
to: toLang
});
const response = await fetch(`https://60s.viki.moe/v2/fanyi?${params}`);
const data = await response.json();
if (data.code === 200 && data.data) {
displayTranslationResult(data.data);
} else {
throw new Error(data.msg || '翻译失败');
}
} catch (error) {
console.error('翻译失败:', error);
showToast('翻译失败: ' + error.message, 'error');
elements.outputText.textContent = '翻译失败,请重试';
} finally {
setTranslating(false);
}
}
// 显示翻译结果
function displayTranslationResult(data) {
// 显示翻译结果
const translation = data.target ? data.target.text : '';
elements.outputText.textContent = translation;
// 更新检测到的语言
if (data.source && data.source.type_desc) {
elements.detectedLang.textContent = `检测: ${data.source.type_desc}`;
}
// 显示发音信息
displayPronunciation(data);
// 如果翻译结果为空
if (!translation) {
elements.outputText.textContent = '未获取到翻译结果';
}
}
// 显示发音信息
function displayPronunciation(data) {
const pronounceSection = elements.pronounceSection;
if (!pronounceSection) {
return;
}
pronounceSection.innerHTML = '';
// 原文发音
if (data.source && data.source.pronounce) {
const sourcePhoneticDiv = document.createElement('div');
sourcePhoneticDiv.className = 'pronounce-item show';
sourcePhoneticDiv.textContent = `原文发音: [${data.source.pronounce}]`;
pronounceSection.appendChild(sourcePhoneticDiv);
}
// 译文发音
if (data.target && data.target.pronounce) {
const targetPhoneticDiv = document.createElement('div');
targetPhoneticDiv.className = 'pronounce-item show';
targetPhoneticDiv.textContent = `译文发音: [${data.target.pronounce}]`;
pronounceSection.appendChild(targetPhoneticDiv);
}
}
// 设置翻译状态
function setTranslating(translating) {
isTranslating = translating;
elements.translateBtn.disabled = translating;
if (translating) {
elements.translateBtn.classList.add('loading');
} else {
elements.translateBtn.classList.remove('loading');
}
}
// 交换语言
function swapLanguages() {
const fromValue = elements.fromLang.value;
const toValue = elements.toLang.value;
// 不能交换自动检测
if (fromValue === 'auto') {
showToast('自动检测语言无法交换', 'error');
return;
}
elements.fromLang.value = toValue;
elements.toLang.value = fromValue;
// 交换文本内容
const inputText = elements.inputText.value;
const outputText = elements.outputText.textContent;
if (outputText && outputText !== '翻译结果将在这里显示...' && outputText !== '翻译失败,请重试' && outputText !== '未获取到翻译结果') {
elements.inputText.value = outputText;
elements.outputText.textContent = inputText;
}
updateCharCount();
updateLanguageLabels();
clearPronunciation();
}
// 清空输入
function clearInput() {
elements.inputText.value = '';
updateCharCount();
clearOutput();
}
// 清空输出
function clearOutput() {
elements.outputText.textContent = '翻译结果将在这里显示...';
clearPronunciation();
}
// 清空发音信息
function clearPronunciation() {
if (elements.pronounceSection) {
elements.pronounceSection.innerHTML = '';
}
}
// 复制输出
function copyOutput() {
const text = elements.outputText.textContent;
if (!text || text === '翻译结果将在这里显示...' || text === '翻译失败,请重试' || text === '未获取到翻译结果') {
showToast('没有可复制的内容', 'error');
return;
}
// 使用现代API复制
if (navigator.clipboard) {
navigator.clipboard.writeText(text).then(() => {
showToast('已复制到剪贴板');
}).catch(() => {
fallbackCopy(text);
});
} else {
fallbackCopy(text);
}
}
// 备用复制方法
function fallbackCopy(text) {
const textArea = document.createElement('textarea');
textArea.value = text;
textArea.style.position = 'fixed';
textArea.style.opacity = '0';
document.body.appendChild(textArea);
textArea.select();
try {
document.execCommand('copy');
showToast('已复制到剪贴板');
} catch (err) {
showToast('复制失败,请手动复制', 'error');
}
document.body.removeChild(textArea);
}
// 显示提示消息
function showToast(message, type = 'success') {
// 移除现有的toast
const existingToast = document.querySelector('.toast');
if (existingToast) {
existingToast.remove();
}
const toast = document.createElement('div');
toast.className = `toast ${type}`;
toast.textContent = message;
document.body.appendChild(toast);
// 显示toast
setTimeout(() => {
toast.classList.add('show');
}, 100);
// 自动隐藏
setTimeout(() => {
toast.classList.remove('show');
setTimeout(() => {
if (toast.parentNode) {
toast.parentNode.removeChild(toast);
}
}, 300);
}, 3000);
}
// 工具函数:防抖
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// 添加键盘快捷键支持
document.addEventListener('keydown', function(e) {
// Ctrl+Enter 翻译
if (e.ctrlKey && e.key === 'Enter') {
e.preventDefault();
translateText();
}
// Ctrl+Shift+C 复制结果
if (e.ctrlKey && e.shiftKey && e.key === 'C') {
e.preventDefault();
copyOutput();
}
// Ctrl+Shift+X 清空输入
if (e.ctrlKey && e.shiftKey && e.key === 'X') {
e.preventDefault();
clearInput();
}
// Ctrl+Shift+S 交换语言
if (e.ctrlKey && e.shiftKey && e.key === 'S') {
e.preventDefault();
swapLanguages();
}
});
// 页面可见性变化时的处理
document.addEventListener('visibilitychange', function() {
if (document.hidden) {
// 页面隐藏时暂停翻译请求
if (isTranslating) {
setTranslating(false);
}
}
});
// 错误处理
window.addEventListener('error', function(e) {
console.error('页面错误:', e.error);
});
window.addEventListener('unhandledrejection', function(e) {
console.error('未处理的Promise拒绝:', e.reason);
// 全局变量
let supportedLanguages = {};
let isTranslating = false;
// DOM元素
const elements = {
fromLang: null,
toLang: null,
inputText: null,
outputText: null,
translateBtn: null,
swapBtn: null,
clearBtn: null,
copyBtn: null,
charCount: null,
detectedLang: null,
targetLang: null,
pronounceSection: null
};
// 初始化
document.addEventListener('DOMContentLoaded', function() {
initializeElements();
loadSupportedLanguages();
bindEvents();
updateCharCount();
});
// 初始化DOM元素
function initializeElements() {
elements.fromLang = document.getElementById('from-lang');
elements.toLang = document.getElementById('to-lang');
elements.inputText = document.getElementById('input-text');
elements.outputText = document.getElementById('output-text');
elements.translateBtn = document.getElementById('translate-btn');
elements.swapBtn = document.getElementById('swap-btn');
elements.clearBtn = document.getElementById('clear-btn');
elements.copyBtn = document.getElementById('copy-btn');
elements.charCount = document.getElementById('char-count');
elements.detectedLang = document.getElementById('detected-lang');
elements.targetLang = document.getElementById('target-lang');
elements.pronounceSection = document.getElementById('pronounce-section');
}
// 加载支持的语言列表
async function loadSupportedLanguages() {
try {
const response = await fetch('https://60s.viki.moe/v2/fanyi/langs');
const data = await response.json();
if (data.code === 200 && data.data && Array.isArray(data.data)) {
// 转换数组格式为对象格式
supportedLanguages = {};
supportedLanguages['auto'] = '自动检测';
data.data.forEach(lang => {
supportedLanguages[lang.code] = lang.label;
});
populateLanguageSelectors();
} else {
throw new Error('获取语言列表失败');
}
} catch (error) {
console.error('加载语言列表失败:', error);
showToast('加载语言列表失败,请刷新页面重试', 'error');
// 使用默认语言列表
useDefaultLanguages();
}
}
// 使用默认语言列表(备用方案)
function useDefaultLanguages() {
supportedLanguages = {
'auto': '自动检测',
'zh-CHS': '中文',
'en': '英语',
'ja': '日语',
'ko': '韩语',
'fr': '法语',
'de': '德语',
'es': '西班牙语',
'ru': '俄语',
'th': '泰语',
'ar': '阿拉伯语',
'pt': '葡萄牙语',
'it': '意大利语'
};
populateLanguageSelectors();
}
// 填充语言选择器
function populateLanguageSelectors() {
const fromSelect = elements.fromLang;
const toSelect = elements.toLang;
// 清空现有选项
fromSelect.innerHTML = '';
toSelect.innerHTML = '';
// 添加语言选项
Object.entries(supportedLanguages).forEach(([code, name]) => {
const fromOption = new Option(name, code);
const toOption = new Option(name, code);
fromSelect.appendChild(fromOption);
toSelect.appendChild(toOption);
});
// 设置默认值
fromSelect.value = 'auto';
toSelect.value = 'en';
// 如果没有auto选项则设置为中文
if (!supportedLanguages['auto']) {
fromSelect.value = 'zh-CHS';
}
}
// 绑定事件
function bindEvents() {
// 输入框事件
elements.inputText.addEventListener('input', function() {
updateCharCount();
clearOutput();
});
elements.inputText.addEventListener('keydown', function(e) {
if (e.ctrlKey && e.key === 'Enter') {
translateText();
}
});
// 按钮事件
elements.translateBtn.addEventListener('click', translateText);
elements.swapBtn.addEventListener('click', swapLanguages);
elements.clearBtn.addEventListener('click', clearInput);
elements.copyBtn.addEventListener('click', copyOutput);
// 语言选择器事件
elements.fromLang.addEventListener('change', function() {
clearOutput();
updateLanguageLabels();
});
elements.toLang.addEventListener('change', function() {
clearOutput();
updateLanguageLabels();
});
}
// 更新字符计数
function updateCharCount() {
const text = elements.inputText.value;
const count = text.length;
elements.charCount.textContent = `${count}/5000`;
if (count > 5000) {
elements.charCount.style.color = '#e74c3c';
} else {
elements.charCount.style.color = '#74c69d';
}
}
// 更新语言标签
function updateLanguageLabels() {
const fromLang = elements.fromLang.value;
const toLang = elements.toLang.value;
elements.detectedLang.textContent = supportedLanguages[fromLang] || '未知语言';
elements.targetLang.textContent = supportedLanguages[toLang] || '未知语言';
}
// 翻译文本
async function translateText() {
const text = elements.inputText.value.trim();
if (!text) {
showToast('请输入要翻译的文本', 'error');
return;
}
if (text.length > 5000) {
showToast('文本长度不能超过5000字符', 'error');
return;
}
if (isTranslating) {
return;
}
setTranslating(true);
try {
const fromLang = elements.fromLang.value;
const toLang = elements.toLang.value;
// 构建请求URL
const params = new URLSearchParams({
text: text,
from: fromLang,
to: toLang
});
const response = await fetch(`https://60s.viki.moe/v2/fanyi?${params}`);
const data = await response.json();
if (data.code === 200 && data.data) {
displayTranslationResult(data.data);
} else {
throw new Error(data.msg || '翻译失败');
}
} catch (error) {
console.error('翻译失败:', error);
showToast('翻译失败: ' + error.message, 'error');
elements.outputText.textContent = '翻译失败,请重试';
} finally {
setTranslating(false);
}
}
// 显示翻译结果
function displayTranslationResult(data) {
// 显示翻译结果
const translation = data.target ? data.target.text : '';
elements.outputText.textContent = translation;
// 更新检测到的语言
if (data.source && data.source.type_desc) {
elements.detectedLang.textContent = `检测: ${data.source.type_desc}`;
}
// 显示发音信息
displayPronunciation(data);
// 如果翻译结果为空
if (!translation) {
elements.outputText.textContent = '未获取到翻译结果';
}
}
// 显示发音信息
function displayPronunciation(data) {
const pronounceSection = elements.pronounceSection;
if (!pronounceSection) {
return;
}
pronounceSection.innerHTML = '';
// 原文发音
if (data.source && data.source.pronounce) {
const sourcePhoneticDiv = document.createElement('div');
sourcePhoneticDiv.className = 'pronounce-item show';
sourcePhoneticDiv.textContent = `原文发音: [${data.source.pronounce}]`;
pronounceSection.appendChild(sourcePhoneticDiv);
}
// 译文发音
if (data.target && data.target.pronounce) {
const targetPhoneticDiv = document.createElement('div');
targetPhoneticDiv.className = 'pronounce-item show';
targetPhoneticDiv.textContent = `译文发音: [${data.target.pronounce}]`;
pronounceSection.appendChild(targetPhoneticDiv);
}
}
// 设置翻译状态
function setTranslating(translating) {
isTranslating = translating;
elements.translateBtn.disabled = translating;
if (translating) {
elements.translateBtn.classList.add('loading');
} else {
elements.translateBtn.classList.remove('loading');
}
}
// 交换语言
function swapLanguages() {
const fromValue = elements.fromLang.value;
const toValue = elements.toLang.value;
// 不能交换自动检测
if (fromValue === 'auto') {
showToast('自动检测语言无法交换', 'error');
return;
}
elements.fromLang.value = toValue;
elements.toLang.value = fromValue;
// 交换文本内容
const inputText = elements.inputText.value;
const outputText = elements.outputText.textContent;
if (outputText && outputText !== '翻译结果将在这里显示...' && outputText !== '翻译失败,请重试' && outputText !== '未获取到翻译结果') {
elements.inputText.value = outputText;
elements.outputText.textContent = inputText;
}
updateCharCount();
updateLanguageLabels();
clearPronunciation();
}
// 清空输入
function clearInput() {
elements.inputText.value = '';
updateCharCount();
clearOutput();
}
// 清空输出
function clearOutput() {
elements.outputText.textContent = '翻译结果将在这里显示...';
clearPronunciation();
}
// 清空发音信息
function clearPronunciation() {
if (elements.pronounceSection) {
elements.pronounceSection.innerHTML = '';
}
}
// 复制输出
function copyOutput() {
const text = elements.outputText.textContent;
if (!text || text === '翻译结果将在这里显示...' || text === '翻译失败,请重试' || text === '未获取到翻译结果') {
showToast('没有可复制的内容', 'error');
return;
}
// 使用现代API复制
if (navigator.clipboard) {
navigator.clipboard.writeText(text).then(() => {
showToast('已复制到剪贴板');
}).catch(() => {
fallbackCopy(text);
});
} else {
fallbackCopy(text);
}
}
// 备用复制方法
function fallbackCopy(text) {
const textArea = document.createElement('textarea');
textArea.value = text;
textArea.style.position = 'fixed';
textArea.style.opacity = '0';
document.body.appendChild(textArea);
textArea.select();
try {
document.execCommand('copy');
showToast('已复制到剪贴板');
} catch (err) {
showToast('复制失败,请手动复制', 'error');
}
document.body.removeChild(textArea);
}
// 显示提示消息
function showToast(message, type = 'success') {
// 移除现有的toast
const existingToast = document.querySelector('.toast');
if (existingToast) {
existingToast.remove();
}
const toast = document.createElement('div');
toast.className = `toast ${type}`;
toast.textContent = message;
document.body.appendChild(toast);
// 显示toast
setTimeout(() => {
toast.classList.add('show');
}, 100);
// 自动隐藏
setTimeout(() => {
toast.classList.remove('show');
setTimeout(() => {
if (toast.parentNode) {
toast.parentNode.removeChild(toast);
}
}, 300);
}, 3000);
}
// 工具函数:防抖
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// 添加键盘快捷键支持
document.addEventListener('keydown', function(e) {
// Ctrl+Enter 翻译
if (e.ctrlKey && e.key === 'Enter') {
e.preventDefault();
translateText();
}
// Ctrl+Shift+C 复制结果
if (e.ctrlKey && e.shiftKey && e.key === 'C') {
e.preventDefault();
copyOutput();
}
// Ctrl+Shift+X 清空输入
if (e.ctrlKey && e.shiftKey && e.key === 'X') {
e.preventDefault();
clearInput();
}
// Ctrl+Shift+S 交换语言
if (e.ctrlKey && e.shiftKey && e.key === 'S') {
e.preventDefault();
swapLanguages();
}
});
// 页面可见性变化时的处理
document.addEventListener('visibilitychange', function() {
if (document.hidden) {
// 页面隐藏时暂停翻译请求
if (isTranslating) {
setTranslating(false);
}
}
});
// 错误处理
window.addEventListener('error', function(e) {
console.error('页面错误:', e.error);
});
window.addEventListener('unhandledrejection', function(e) {
console.error('未处理的Promise拒绝:', e.reason);
});

View File

@@ -1,441 +1,441 @@
/* 基础样式重置 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
line-height: 1.6;
color: #2d5a3d;
min-height: 100vh;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
min-height: 100vh;
display: flex;
flex-direction: column;
}
/* 头部样式 */
.header {
text-align: center;
margin-bottom: 30px;
}
.header h1 {
font-size: 2.5rem;
color: #1a4d2e;
margin-bottom: 10px;
font-weight: 700;
}
.subtitle {
font-size: 1.1rem;
color: #4a7c59;
opacity: 0.9;
}
/* 主要内容区域 */
.main-content {
flex: 1;
display: flex;
justify-content: center;
align-items: flex-start;
}
.translate-box {
width: 100%;
max-width: 900px;
border-radius: 20px;
padding: 30px;
box-shadow: 0 10px 30px rgba(26, 77, 46, 0.1);
}
/* 语言选择器 */
.language-selector {
display: flex;
align-items: center;
gap: 20px;
margin-bottom: 25px;
justify-content: center;
}
.lang-group {
display: flex;
flex-direction: column;
gap: 8px;
flex: 1;
max-width: 200px;
}
.lang-group label {
font-size: 0.9rem;
color: #2d5a3d;
font-weight: 500;
}
.lang-select {
padding: 12px 16px;
border: 2px solid #74c69d;
border-radius: 12px;
color: #2d5a3d;
font-size: 1rem;
cursor: pointer;
transition: all 0.3s ease;
}
.lang-select:focus {
outline: none;
border-color: #52b788;
box-shadow: 0 0 0 3px rgba(116, 198, 157, 0.2);
}
.lang-select:hover {
border-color: #52b788;
}
.swap-btn {
border: none;
border-radius: 50%;
width: 45px;
height: 45px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s ease;
color: white;
margin-top: 25px;
}
.swap-btn:hover {
transform: rotate(180deg);
}
/* 文本区域 */
.text-areas {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
margin-bottom: 25px;
}
.input-section,
.output-section {
display: flex;
flex-direction: column;
}
.textarea-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
padding: 0 5px;
}
.detected-lang,
.target-lang {
font-size: 0.9rem;
color: #4a7c59;
font-weight: 500;
}
.clear-btn,
.copy-btn {
background: none;
border: none;
color: #74c69d;
cursor: pointer;
padding: 5px;
border-radius: 6px;
transition: all 0.3s ease;
}
.clear-btn:hover,
.copy-btn:hover {
color: #52b788;
}
#input-text {
width: 100%;
height: 200px;
padding: 16px;
border: 2px solid #74c69d;
border-radius: 12px;
font-size: 1rem;
color: #2d5a3d;
resize: vertical;
transition: all 0.3s ease;
font-family: inherit;
}
#input-text:focus {
outline: none;
border-color: #52b788;
box-shadow: 0 0 0 3px rgba(116, 198, 157, 0.2);
}
#input-text::placeholder {
color: #74c69d;
opacity: 0.7;
}
.output-text {
width: 100%;
height: 200px;
padding: 16px;
border: 2px solid #b7e4c7;
border-radius: 12px;
font-size: 1rem;
color: #2d5a3d;
overflow-y: auto;
line-height: 1.6;
}
.char-count {
text-align: right;
font-size: 0.8rem;
color: #74c69d;
margin-top: 5px;
}
.pronounce-section {
margin-top: 10px;
display: flex;
flex-direction: column;
gap: 5px;
}
.pronounce-item {
font-size: 0.9rem;
color: #4a7c59;
font-style: italic;
padding: 5px 10px;
border-radius: 8px;
display: none;
}
.pronounce-item.show {
display: block;
}
/* 操作按钮 */
.action-buttons {
display: flex;
justify-content: center;
}
.translate-btn {
color: white;
border: none;
padding: 15px 40px;
border-radius: 25px;
font-size: 1.1rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
min-width: 120px;
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
}
.translate-btn:hover {
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(116, 198, 157, 0.3);
}
.translate-btn:active {
transform: translateY(0);
}
.translate-btn:disabled {
cursor: not-allowed;
transform: none;
box-shadow: none;
}
.loading-spinner {
width: 20px;
height: 20px;
border: 2px solid rgba(255, 255, 255, 0.3);
border-top: 2px solid white;
border-radius: 50%;
animation: spin 1s linear infinite;
display: none;
}
.translate-btn.loading .btn-text {
display: none;
}
.translate-btn.loading .loading-spinner {
display: block;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* 页脚 */
.footer {
text-align: center;
margin-top: 30px;
padding: 20px;
color: #4a7c59;
font-size: 0.9rem;
opacity: 0.8;
}
/* 提示消息 */
.toast {
position: fixed;
top: 20px;
right: 20px;
color: white;
padding: 12px 20px;
border-radius: 8px;
font-size: 0.9rem;
transform: translateX(100%);
transition: transform 0.3s ease;
z-index: 1000;
box-shadow: 0 4px 12px rgba(82, 183, 136, 0.3);
}
.toast.show {
transform: translateX(0);
}
.toast.error {
box-shadow: 0 4px 12px rgba(231, 76, 60, 0.3);
}
/* 平板适配 (768px - 1024px) */
@media (max-width: 1024px) and (min-width: 768px) {
.container {
padding: 15px;
}
.header h1 {
font-size: 2.2rem;
}
.translate-box {
padding: 25px;
}
.language-selector {
gap: 15px;
}
.text-areas {
gap: 15px;
}
#input-text,
.output-text {
height: 180px;
}
}
/* 手机端适配 (最大768px) */
@media (max-width: 767px) {
.container {
padding: 10px;
}
.header h1 {
font-size: 1.8rem;
}
.subtitle {
font-size: 1rem;
}
.translate-box {
padding: 20px 15px;
border-radius: 15px;
}
.language-selector {
flex-direction: column;
gap: 15px;
align-items: stretch;
}
.lang-group {
max-width: none;
}
.swap-btn {
align-self: center;
margin-top: 0;
order: 2;
}
.text-areas {
grid-template-columns: 1fr;
gap: 20px;
}
#input-text,
.output-text {
height: 150px;
font-size: 0.95rem;
}
.translate-btn {
padding: 12px 30px;
font-size: 1rem;
width: 100%;
max-width: 200px;
}
.toast {
right: 10px;
left: 10px;
transform: translateY(-100%);
}
.toast.show {
transform: translateY(0);
}
}
/* 超小屏幕适配 (最大480px) */
@media (max-width: 480px) {
.header h1 {
font-size: 1.6rem;
}
.translate-box {
padding: 15px 10px;
}
.lang-select {
padding: 10px 12px;
font-size: 0.9rem;
}
#input-text,
.output-text {
height: 120px;
padding: 12px;
font-size: 0.9rem;
}
.translate-btn {
padding: 10px 25px;
font-size: 0.95rem;
}
/* 基础样式重置 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
line-height: 1.6;
color: #2d5a3d;
min-height: 100vh;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
min-height: 100vh;
display: flex;
flex-direction: column;
}
/* 头部样式 */
.header {
text-align: center;
margin-bottom: 30px;
}
.header h1 {
font-size: 2.5rem;
color: #1a4d2e;
margin-bottom: 10px;
font-weight: 700;
}
.subtitle {
font-size: 1.1rem;
color: #4a7c59;
opacity: 0.9;
}
/* 主要内容区域 */
.main-content {
flex: 1;
display: flex;
justify-content: center;
align-items: flex-start;
}
.translate-box {
width: 100%;
max-width: 900px;
border-radius: 20px;
padding: 30px;
box-shadow: 0 10px 30px rgba(26, 77, 46, 0.1);
}
/* 语言选择器 */
.language-selector {
display: flex;
align-items: center;
gap: 20px;
margin-bottom: 25px;
justify-content: center;
}
.lang-group {
display: flex;
flex-direction: column;
gap: 8px;
flex: 1;
max-width: 200px;
}
.lang-group label {
font-size: 0.9rem;
color: #2d5a3d;
font-weight: 500;
}
.lang-select {
padding: 12px 16px;
border: 2px solid #74c69d;
border-radius: 12px;
color: #2d5a3d;
font-size: 1rem;
cursor: pointer;
transition: all 0.3s ease;
}
.lang-select:focus {
outline: none;
border-color: #52b788;
box-shadow: 0 0 0 3px rgba(116, 198, 157, 0.2);
}
.lang-select:hover {
border-color: #52b788;
}
.swap-btn {
border: none;
border-radius: 50%;
width: 45px;
height: 45px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s ease;
color: white;
margin-top: 25px;
}
.swap-btn:hover {
transform: rotate(180deg);
}
/* 文本区域 */
.text-areas {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
margin-bottom: 25px;
}
.input-section,
.output-section {
display: flex;
flex-direction: column;
}
.textarea-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
padding: 0 5px;
}
.detected-lang,
.target-lang {
font-size: 0.9rem;
color: #4a7c59;
font-weight: 500;
}
.clear-btn,
.copy-btn {
background: none;
border: none;
color: #74c69d;
cursor: pointer;
padding: 5px;
border-radius: 6px;
transition: all 0.3s ease;
}
.clear-btn:hover,
.copy-btn:hover {
color: #52b788;
}
#input-text {
width: 100%;
height: 200px;
padding: 16px;
border: 2px solid #74c69d;
border-radius: 12px;
font-size: 1rem;
color: #2d5a3d;
resize: vertical;
transition: all 0.3s ease;
font-family: inherit;
}
#input-text:focus {
outline: none;
border-color: #52b788;
box-shadow: 0 0 0 3px rgba(116, 198, 157, 0.2);
}
#input-text::placeholder {
color: #74c69d;
opacity: 0.7;
}
.output-text {
width: 100%;
height: 200px;
padding: 16px;
border: 2px solid #b7e4c7;
border-radius: 12px;
font-size: 1rem;
color: #2d5a3d;
overflow-y: auto;
line-height: 1.6;
}
.char-count {
text-align: right;
font-size: 0.8rem;
color: #74c69d;
margin-top: 5px;
}
.pronounce-section {
margin-top: 10px;
display: flex;
flex-direction: column;
gap: 5px;
}
.pronounce-item {
font-size: 0.9rem;
color: #4a7c59;
font-style: italic;
padding: 5px 10px;
border-radius: 8px;
display: none;
}
.pronounce-item.show {
display: block;
}
/* 操作按钮 */
.action-buttons {
display: flex;
justify-content: center;
}
.translate-btn {
color: white;
border: none;
padding: 15px 40px;
border-radius: 25px;
font-size: 1.1rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
min-width: 120px;
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
}
.translate-btn:hover {
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(116, 198, 157, 0.3);
}
.translate-btn:active {
transform: translateY(0);
}
.translate-btn:disabled {
cursor: not-allowed;
transform: none;
box-shadow: none;
}
.loading-spinner {
width: 20px;
height: 20px;
border: 2px solid rgba(255, 255, 255, 0.3);
border-top: 2px solid white;
border-radius: 50%;
animation: spin 1s linear infinite;
display: none;
}
.translate-btn.loading .btn-text {
display: none;
}
.translate-btn.loading .loading-spinner {
display: block;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* 页脚 */
.footer {
text-align: center;
margin-top: 30px;
padding: 20px;
color: #4a7c59;
font-size: 0.9rem;
opacity: 0.8;
}
/* 提示消息 */
.toast {
position: fixed;
top: 20px;
right: 20px;
color: white;
padding: 12px 20px;
border-radius: 8px;
font-size: 0.9rem;
transform: translateX(100%);
transition: transform 0.3s ease;
z-index: 1000;
box-shadow: 0 4px 12px rgba(82, 183, 136, 0.3);
}
.toast.show {
transform: translateX(0);
}
.toast.error {
box-shadow: 0 4px 12px rgba(231, 76, 60, 0.3);
}
/* 平板适配 (768px - 1024px) */
@media (max-width: 1024px) and (min-width: 768px) {
.container {
padding: 15px;
}
.header h1 {
font-size: 2.2rem;
}
.translate-box {
padding: 25px;
}
.language-selector {
gap: 15px;
}
.text-areas {
gap: 15px;
}
#input-text,
.output-text {
height: 180px;
}
}
/* 手机端适配 (最大768px) */
@media (max-width: 767px) {
.container {
padding: 10px;
}
.header h1 {
font-size: 1.8rem;
}
.subtitle {
font-size: 1rem;
}
.translate-box {
padding: 20px 15px;
border-radius: 15px;
}
.language-selector {
flex-direction: column;
gap: 15px;
align-items: stretch;
}
.lang-group {
max-width: none;
}
.swap-btn {
align-self: center;
margin-top: 0;
order: 2;
}
.text-areas {
grid-template-columns: 1fr;
gap: 20px;
}
#input-text,
.output-text {
height: 150px;
font-size: 0.95rem;
}
.translate-btn {
padding: 12px 30px;
font-size: 1rem;
width: 100%;
max-width: 200px;
}
.toast {
right: 10px;
left: 10px;
transform: translateY(-100%);
}
.toast.show {
transform: translateY(0);
}
}
/* 超小屏幕适配 (最大480px) */
@media (max-width: 480px) {
.header h1 {
font-size: 1.6rem;
}
.translate-box {
padding: 15px 10px;
}
.lang-select {
padding: 10px 12px;
font-size: 0.9rem;
}
#input-text,
.output-text {
height: 120px;
padding: 12px;
font-size: 0.9rem;
}
.translate-btn {
padding: 10px 25px;
font-size: 0.95rem;
}
}

View File

@@ -1,145 +1,145 @@
/* 背景样式文件 */
body {
background: linear-gradient(135deg, #e8f5e8 0%, #d4f1d4 25%, #c8ecc8 50%, #b8e6b8 75%, #a8d5ba 100%);
background-attachment: fixed;
background-size: 400% 400%;
animation: gradientShift 15s ease infinite;
position: relative;
overflow-x: hidden;
}
/* 背景渐变动画 */
@keyframes gradientShift {
0% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
}
/* 背景装饰元素 */
body::before {
content: '';
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image:
radial-gradient(circle at 20% 80%, rgba(168, 213, 186, 0.1) 0%, transparent 50%),
radial-gradient(circle at 80% 20%, rgba(107, 183, 123, 0.1) 0%, transparent 50%),
radial-gradient(circle at 40% 40%, rgba(200, 236, 200, 0.1) 0%, transparent 50%);
pointer-events: none;
z-index: -1;
}
/* 浮动装饰圆点 */
body::after {
content: '';
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image:
radial-gradient(2px 2px at 20px 30px, rgba(168, 213, 186, 0.3), transparent),
radial-gradient(2px 2px at 40px 70px, rgba(107, 183, 123, 0.2), transparent),
radial-gradient(1px 1px at 90px 40px, rgba(200, 236, 200, 0.4), transparent),
radial-gradient(1px 1px at 130px 80px, rgba(168, 213, 186, 0.2), transparent),
radial-gradient(2px 2px at 160px 30px, rgba(107, 183, 123, 0.3), transparent);
background-repeat: repeat;
background-size: 200px 100px;
animation: float 20s linear infinite;
pointer-events: none;
z-index: -1;
}
@keyframes float {
0% {
transform: translateY(0px);
}
50% {
transform: translateY(-10px);
}
100% {
transform: translateY(0px);
}
}
/* 云朵装饰效果 */
.container::before {
content: '';
position: absolute;
top: -50px;
right: -50px;
width: 200px;
height: 100px;
background: rgba(255, 255, 255, 0.1);
border-radius: 50px;
box-shadow:
-30px 20px 0 rgba(255, 255, 255, 0.08),
30px 40px 0 rgba(255, 255, 255, 0.06);
animation: cloudFloat 25s ease-in-out infinite;
pointer-events: none;
z-index: -1;
}
.container::after {
content: '';
position: absolute;
bottom: -30px;
left: -30px;
width: 150px;
height: 80px;
background: rgba(255, 255, 255, 0.08);
border-radius: 40px;
box-shadow:
20px 15px 0 rgba(255, 255, 255, 0.06),
-20px 25px 0 rgba(255, 255, 255, 0.04);
animation: cloudFloat 30s ease-in-out infinite reverse;
pointer-events: none;
z-index: -1;
}
@keyframes cloudFloat {
0%, 100% {
transform: translateX(0px) translateY(0px);
}
25% {
transform: translateX(20px) translateY(-10px);
}
50% {
transform: translateX(-10px) translateY(-20px);
}
75% {
transform: translateX(15px) translateY(-5px);
}
}
/* 响应式背景调整 */
@media (max-width: 768px) {
body::after {
background-size: 150px 75px;
}
.container::before,
.container::after {
display: none;
}
}
@media (max-width: 480px) {
body {
background: linear-gradient(135deg, #e8f5e8 0%, #d4f1d4 50%, #a8d5ba 100%);
animation: none;
}
body::before,
body::after {
display: none;
}
/* 背景样式文件 */
body {
background: linear-gradient(135deg, #e8f5e8 0%, #d4f1d4 25%, #c8ecc8 50%, #b8e6b8 75%, #a8d5ba 100%);
background-attachment: fixed;
background-size: 400% 400%;
animation: gradientShift 15s ease infinite;
position: relative;
overflow-x: hidden;
}
/* 背景渐变动画 */
@keyframes gradientShift {
0% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
}
/* 背景装饰元素 */
body::before {
content: '';
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image:
radial-gradient(circle at 20% 80%, rgba(168, 213, 186, 0.1) 0%, transparent 50%),
radial-gradient(circle at 80% 20%, rgba(107, 183, 123, 0.1) 0%, transparent 50%),
radial-gradient(circle at 40% 40%, rgba(200, 236, 200, 0.1) 0%, transparent 50%);
pointer-events: none;
z-index: -1;
}
/* 浮动装饰圆点 */
body::after {
content: '';
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image:
radial-gradient(2px 2px at 20px 30px, rgba(168, 213, 186, 0.3), transparent),
radial-gradient(2px 2px at 40px 70px, rgba(107, 183, 123, 0.2), transparent),
radial-gradient(1px 1px at 90px 40px, rgba(200, 236, 200, 0.4), transparent),
radial-gradient(1px 1px at 130px 80px, rgba(168, 213, 186, 0.2), transparent),
radial-gradient(2px 2px at 160px 30px, rgba(107, 183, 123, 0.3), transparent);
background-repeat: repeat;
background-size: 200px 100px;
animation: float 20s linear infinite;
pointer-events: none;
z-index: -1;
}
@keyframes float {
0% {
transform: translateY(0px);
}
50% {
transform: translateY(-10px);
}
100% {
transform: translateY(0px);
}
}
/* 云朵装饰效果 */
.container::before {
content: '';
position: absolute;
top: -50px;
right: -50px;
width: 200px;
height: 100px;
background: rgba(255, 255, 255, 0.1);
border-radius: 50px;
box-shadow:
-30px 20px 0 rgba(255, 255, 255, 0.08),
30px 40px 0 rgba(255, 255, 255, 0.06);
animation: cloudFloat 25s ease-in-out infinite;
pointer-events: none;
z-index: -1;
}
.container::after {
content: '';
position: absolute;
bottom: -30px;
left: -30px;
width: 150px;
height: 80px;
background: rgba(255, 255, 255, 0.08);
border-radius: 40px;
box-shadow:
20px 15px 0 rgba(255, 255, 255, 0.06),
-20px 25px 0 rgba(255, 255, 255, 0.04);
animation: cloudFloat 30s ease-in-out infinite reverse;
pointer-events: none;
z-index: -1;
}
@keyframes cloudFloat {
0%, 100% {
transform: translateX(0px) translateY(0px);
}
25% {
transform: translateX(20px) translateY(-10px);
}
50% {
transform: translateX(-10px) translateY(-20px);
}
75% {
transform: translateX(15px) translateY(-5px);
}
}
/* 响应式背景调整 */
@media (max-width: 768px) {
body::after {
background-size: 150px 75px;
}
.container::before,
.container::after {
display: none;
}
}
@media (max-width: 480px) {
body {
background: linear-gradient(135deg, #e8f5e8 0%, #d4f1d4 50%, #a8d5ba 100%);
animation: none;
}
body::before,
body::after {
display: none;
}
}

View File

@@ -1,412 +1,412 @@
/* 基础样式重置 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Microsoft YaHei', Arial, sans-serif;
line-height: 1.6;
color: #333;
min-height: 100vh;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
/* 头部样式 */
.header {
text-align: center;
margin-bottom: 30px;
}
.header h1 {
color: #2d5a3d;
font-size: 2.5rem;
font-weight: 300;
margin-bottom: 10px;
}
/* 搜索区域 */
.search-section {
margin-bottom: 30px;
}
.search-box {
display: flex;
justify-content: center;
gap: 10px;
max-width: 500px;
margin: 0 auto;
}
#cityInput {
flex: 1;
padding: 12px 16px;
border: 2px solid #a8d5ba;
border-radius: 25px;
font-size: 16px;
outline: none;
transition: all 0.3s ease;
background: rgba(255, 255, 255, 0.9);
}
#cityInput:focus {
border-color: #6bb77b;
box-shadow: 0 0 10px rgba(107, 183, 123, 0.3);
}
#searchBtn {
padding: 12px 24px;
background: linear-gradient(135deg, #6bb77b, #5a9f6a);
color: white;
border: none;
border-radius: 25px;
font-size: 16px;
cursor: pointer;
transition: all 0.3s ease;
white-space: nowrap;
}
#searchBtn:hover {
background: linear-gradient(135deg, #5a9f6a, #4a8759);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(107, 183, 123, 0.4);
}
/* 加载动画 */
.loading {
text-align: center;
padding: 40px;
}
.spinner {
width: 40px;
height: 40px;
border: 4px solid #e8f5e8;
border-top: 4px solid #6bb77b;
border-radius: 50%;
animation: spin 1s linear infinite;
margin: 0 auto 20px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* 天气容器 */
.weather-container {
background: rgba(255, 255, 255, 0.95);
border-radius: 20px;
padding: 30px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
backdrop-filter: blur(10px);
border: 1px solid rgba(168, 213, 186, 0.3);
}
/* 位置信息 */
.location-info {
text-align: center;
margin-bottom: 30px;
padding-bottom: 20px;
border-bottom: 2px solid #e8f5e8;
}
.location-info h2 {
color: #2d5a3d;
font-size: 2rem;
margin-bottom: 5px;
}
.location-info p {
color: #666;
font-size: 14px;
}
/* 当前天气 */
.current-weather {
margin-bottom: 30px;
}
.weather-main {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 25px;
}
.temperature {
font-size: 4rem;
font-weight: 300;
color: #2d5a3d;
}
.unit {
font-size: 2rem;
color: #6bb77b;
}
.weather-desc p:first-child {
font-size: 1.5rem;
color: #2d5a3d;
margin-bottom: 5px;
}
.weather-desc p:last-child {
color: #666;
font-size: 14px;
}
/* 更新时间 */
.update-time {
text-align: center;
margin-top: 30px;
padding-top: 20px;
border-top: 1px solid #e8f5e8;
color: #666;
font-size: 14px;
}
/* 错误信息 */
.error-message {
text-align: center;
padding: 40px;
background: rgba(255, 107, 107, 0.1);
border-radius: 15px;
border: 1px solid rgba(255, 107, 107, 0.2);
color: #d63031;
}
/* 平板端适配 (768px - 1024px) */
@media (min-width: 768px) and (max-width: 1024px) {
.container {
padding: 25px;
}
.header h1 {
font-size: 2.2rem;
}
.temperature {
font-size: 3.5rem;
}
.index-grid {
grid-template-columns: repeat(2, 1fr);
}
}
/* 电脑端适配 (1024px+) */
@media (min-width: 1024px) {
.container {
padding: 40px;
}
.weather-container {
padding: 40px;
}
.weather-main {
justify-content: space-around;
}
.index-grid {
grid-template-columns: repeat(3, 1fr);
}
.search-box {
max-width: 600px;
}
}
/* 手机端适配 (768px以下) */
@media (max-width: 767px) {
.container {
padding: 15px;
}
.header h1 {
font-size: 2rem;
}
.search-box {
flex-direction: column;
gap: 15px;
}
#searchBtn {
padding: 14px 24px;
}
.weather-container {
padding: 20px;
margin: 0 -5px;
}
.weather-main {
flex-direction: column;
text-align: center;
gap: 20px;
}
.temperature {
font-size: 3rem;
}
.index-grid {
grid-template-columns: 1fr;
gap: 15px;
}
.index-item {
padding: 15px;
}
.index-icon {
font-size: 1.5rem;
width: 40px;
margin-right: 12px;
}
}
/* 超小屏幕适配 (480px以下) */
@media (max-width: 480px) {
.container {
padding: 10px;
}
.header h1 {
font-size: 1.8rem;
}
.weather-container {
padding: 15px;
border-radius: 15px;
}
.temperature {
font-size: 2.5rem;
}
}
/* 预报区域样式 */
.forecast-section {
margin-top: 30px;
padding: 20px;
background: rgba(255, 255, 255, 0.95);
border-radius: 20px;
backdrop-filter: blur(10px);
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
}
.forecast-section h3 {
color: #2d5a3d;
font-size: 1.5rem;
margin-bottom: 20px;
text-align: center;
}
.forecast-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
}
.forecast-item {
background: rgba(255, 255, 255, 0.8);
border-radius: 15px;
padding: 15px;
text-align: center;
border: 1px solid rgba(168, 213, 186, 0.3);
transition: all 0.3s ease;
}
.forecast-item:hover {
transform: translateY(-5px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
}
.forecast-date {
font-weight: bold;
color: #2d5a3d;
margin-bottom: 10px;
font-size: 1.1rem;
}
.forecast-weather {
margin-bottom: 10px;
}
.weather-day {
color: #666;
font-size: 0.9rem;
}
.weather-night {
color: #888;
font-size: 0.85rem;
}
.forecast-temp {
margin-bottom: 10px;
display: flex;
justify-content: center;
gap: 10px;
}
.temp-high {
color: #ff6b6b;
font-weight: bold;
font-size: 1.2rem;
}
.temp-low {
color: #4ecdc4;
font-size: 1rem;
}
.forecast-wind {
color: #666;
font-size: 0.85rem;
margin-bottom: 5px;
}
.forecast-humidity {
color: #888;
font-size: 0.8rem;
}
/* 预报区域响应式设计 */
@media (max-width: 768px) {
.forecast-grid {
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 10px;
}
.forecast-item {
padding: 12px;
}
.forecast-date {
font-size: 1rem;
}
.temp-high {
font-size: 1.1rem;
}
}
@media (max-width: 480px) {
.forecast-section {
padding: 15px;
}
.forecast-grid {
grid-template-columns: repeat(2, 1fr);
}
/* 基础样式重置 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Microsoft YaHei', Arial, sans-serif;
line-height: 1.6;
color: #333;
min-height: 100vh;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
/* 头部样式 */
.header {
text-align: center;
margin-bottom: 30px;
}
.header h1 {
color: #2d5a3d;
font-size: 2.5rem;
font-weight: 300;
margin-bottom: 10px;
}
/* 搜索区域 */
.search-section {
margin-bottom: 30px;
}
.search-box {
display: flex;
justify-content: center;
gap: 10px;
max-width: 500px;
margin: 0 auto;
}
#cityInput {
flex: 1;
padding: 12px 16px;
border: 2px solid #a8d5ba;
border-radius: 25px;
font-size: 16px;
outline: none;
transition: all 0.3s ease;
background: rgba(255, 255, 255, 0.9);
}
#cityInput:focus {
border-color: #6bb77b;
box-shadow: 0 0 10px rgba(107, 183, 123, 0.3);
}
#searchBtn {
padding: 12px 24px;
background: linear-gradient(135deg, #6bb77b, #5a9f6a);
color: white;
border: none;
border-radius: 25px;
font-size: 16px;
cursor: pointer;
transition: all 0.3s ease;
white-space: nowrap;
}
#searchBtn:hover {
background: linear-gradient(135deg, #5a9f6a, #4a8759);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(107, 183, 123, 0.4);
}
/* 加载动画 */
.loading {
text-align: center;
padding: 40px;
}
.spinner {
width: 40px;
height: 40px;
border: 4px solid #e8f5e8;
border-top: 4px solid #6bb77b;
border-radius: 50%;
animation: spin 1s linear infinite;
margin: 0 auto 20px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* 天气容器 */
.weather-container {
background: rgba(255, 255, 255, 0.95);
border-radius: 20px;
padding: 30px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
backdrop-filter: blur(10px);
border: 1px solid rgba(168, 213, 186, 0.3);
}
/* 位置信息 */
.location-info {
text-align: center;
margin-bottom: 30px;
padding-bottom: 20px;
border-bottom: 2px solid #e8f5e8;
}
.location-info h2 {
color: #2d5a3d;
font-size: 2rem;
margin-bottom: 5px;
}
.location-info p {
color: #666;
font-size: 14px;
}
/* 当前天气 */
.current-weather {
margin-bottom: 30px;
}
.weather-main {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 25px;
}
.temperature {
font-size: 4rem;
font-weight: 300;
color: #2d5a3d;
}
.unit {
font-size: 2rem;
color: #6bb77b;
}
.weather-desc p:first-child {
font-size: 1.5rem;
color: #2d5a3d;
margin-bottom: 5px;
}
.weather-desc p:last-child {
color: #666;
font-size: 14px;
}
/* 更新时间 */
.update-time {
text-align: center;
margin-top: 30px;
padding-top: 20px;
border-top: 1px solid #e8f5e8;
color: #666;
font-size: 14px;
}
/* 错误信息 */
.error-message {
text-align: center;
padding: 40px;
background: rgba(255, 107, 107, 0.1);
border-radius: 15px;
border: 1px solid rgba(255, 107, 107, 0.2);
color: #d63031;
}
/* 平板端适配 (768px - 1024px) */
@media (min-width: 768px) and (max-width: 1024px) {
.container {
padding: 25px;
}
.header h1 {
font-size: 2.2rem;
}
.temperature {
font-size: 3.5rem;
}
.index-grid {
grid-template-columns: repeat(2, 1fr);
}
}
/* 电脑端适配 (1024px+) */
@media (min-width: 1024px) {
.container {
padding: 40px;
}
.weather-container {
padding: 40px;
}
.weather-main {
justify-content: space-around;
}
.index-grid {
grid-template-columns: repeat(3, 1fr);
}
.search-box {
max-width: 600px;
}
}
/* 手机端适配 (768px以下) */
@media (max-width: 767px) {
.container {
padding: 15px;
}
.header h1 {
font-size: 2rem;
}
.search-box {
flex-direction: column;
gap: 15px;
}
#searchBtn {
padding: 14px 24px;
}
.weather-container {
padding: 20px;
margin: 0 -5px;
}
.weather-main {
flex-direction: column;
text-align: center;
gap: 20px;
}
.temperature {
font-size: 3rem;
}
.index-grid {
grid-template-columns: 1fr;
gap: 15px;
}
.index-item {
padding: 15px;
}
.index-icon {
font-size: 1.5rem;
width: 40px;
margin-right: 12px;
}
}
/* 超小屏幕适配 (480px以下) */
@media (max-width: 480px) {
.container {
padding: 10px;
}
.header h1 {
font-size: 1.8rem;
}
.weather-container {
padding: 15px;
border-radius: 15px;
}
.temperature {
font-size: 2.5rem;
}
}
/* 预报区域样式 */
.forecast-section {
margin-top: 30px;
padding: 20px;
background: rgba(255, 255, 255, 0.95);
border-radius: 20px;
backdrop-filter: blur(10px);
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
}
.forecast-section h3 {
color: #2d5a3d;
font-size: 1.5rem;
margin-bottom: 20px;
text-align: center;
}
.forecast-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
}
.forecast-item {
background: rgba(255, 255, 255, 0.8);
border-radius: 15px;
padding: 15px;
text-align: center;
border: 1px solid rgba(168, 213, 186, 0.3);
transition: all 0.3s ease;
}
.forecast-item:hover {
transform: translateY(-5px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
}
.forecast-date {
font-weight: bold;
color: #2d5a3d;
margin-bottom: 10px;
font-size: 1.1rem;
}
.forecast-weather {
margin-bottom: 10px;
}
.weather-day {
color: #666;
font-size: 0.9rem;
}
.weather-night {
color: #888;
font-size: 0.85rem;
}
.forecast-temp {
margin-bottom: 10px;
display: flex;
justify-content: center;
gap: 10px;
}
.temp-high {
color: #ff6b6b;
font-weight: bold;
font-size: 1.2rem;
}
.temp-low {
color: #4ecdc4;
font-size: 1rem;
}
.forecast-wind {
color: #666;
font-size: 0.85rem;
margin-bottom: 5px;
}
.forecast-humidity {
color: #888;
font-size: 0.8rem;
}
/* 预报区域响应式设计 */
@media (max-width: 768px) {
.forecast-grid {
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 10px;
}
.forecast-item {
padding: 12px;
}
.forecast-date {
font-size: 1rem;
}
.temp-high {
font-size: 1.1rem;
}
}
@media (max-width: 480px) {
.forecast-section {
padding: 15px;
}
.forecast-grid {
grid-template-columns: repeat(2, 1fr);
}
}

View File

@@ -1,66 +1,66 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>天气预报</title>
<link rel="stylesheet" href="css/style.css">
<link rel="stylesheet" href="css/background.css">
</head>
<body>
<div class="container">
<header class="header">
<h1>天气预报</h1>
</header>
<div class="search-section">
<div class="search-box">
<input type="text" id="cityInput" placeholder="请输入城市名称(如:北京)" value="北京">
<button id="searchBtn">查询天气</button>
</div>
</div>
<div class="loading" id="loading" style="display: none;">
<div class="spinner"></div>
<p>正在获取天气信息...</p>
</div>
<div class="weather-container" id="weatherContainer" style="display: none;">
<div class="location-info">
<h2 id="locationName"></h2>
<p id="locationDetail"></p>
</div>
<div class="current-weather">
<div class="weather-main">
<div class="temperature">
<span id="temperature"></span>
<span class="unit">°C</span>
</div>
<div class="weather-desc">
<p id="weatherCondition"></p>
<p id="feelsLike"></p>
</div>
</div>
</div>
<div class="forecast-section">
<h3>未来天气预报</h3>
<div class="forecast-grid" id="forecastGrid">
<!-- 预报数据将通过JavaScript动态生成 -->
</div>
</div>
<div class="update-time">
<p>更新时间:<span id="updateTime"></span></p>
</div>
</div>
<div class="error-message" id="errorMessage" style="display: none;">
<p>获取天气信息失败,请稍后重试</p>
</div>
</div>
<script src="js/script.js"></script>
</body>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>天气预报</title>
<link rel="stylesheet" href="css/style.css">
<link rel="stylesheet" href="css/background.css">
</head>
<body>
<div class="container">
<header class="header">
<h1>天气预报</h1>
</header>
<div class="search-section">
<div class="search-box">
<input type="text" id="cityInput" placeholder="请输入城市名称(如:北京)" value="北京">
<button id="searchBtn">查询天气</button>
</div>
</div>
<div class="loading" id="loading" style="display: none;">
<div class="spinner"></div>
<p>正在获取天气信息...</p>
</div>
<div class="weather-container" id="weatherContainer" style="display: none;">
<div class="location-info">
<h2 id="locationName"></h2>
<p id="locationDetail"></p>
</div>
<div class="current-weather">
<div class="weather-main">
<div class="temperature">
<span id="temperature"></span>
<span class="unit">°C</span>
</div>
<div class="weather-desc">
<p id="weatherCondition"></p>
<p id="feelsLike"></p>
</div>
</div>
</div>
<div class="forecast-section">
<h3>未来天气预报</h3>
<div class="forecast-grid" id="forecastGrid">
<!-- 预报数据将通过JavaScript动态生成 -->
</div>
</div>
<div class="update-time">
<p>更新时间:<span id="updateTime"></span></p>
</div>
</div>
<div class="error-message" id="errorMessage" style="display: none;">
<p>获取天气信息失败,请稍后重试</p>
</div>
</div>
<script src="js/script.js"></script>
</body>
</html>

View File

@@ -1,260 +1,260 @@
// 天气查询应用
class WeatherApp {
constructor() {
this.apiEndpoints = [
"https://60s.api.shumengya.top/v2/weather/forecast",
"https://60s-cf.viki.moe/v2/weather/forecast"
];
this.currentEndpointIndex = 0;
this.init();
}
init() {
this.bindEvents();
// 页面加载时自动查询北京天气
this.searchWeather('北京');
}
bindEvents() {
const searchBtn = document.getElementById('searchBtn');
const cityInput = document.getElementById('cityInput');
searchBtn.addEventListener('click', () => {
const city = cityInput.value.trim();
if (city) {
this.searchWeather(city);
} else {
this.showError('请输入城市名称');
}
});
cityInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
const city = cityInput.value.trim();
if (city) {
this.searchWeather(city);
} else {
this.showError('请输入城市名称');
}
}
});
// 防止输入框为空时查询
cityInput.addEventListener('input', () => {
const searchBtn = document.getElementById('searchBtn');
searchBtn.disabled = !cityInput.value.trim();
});
}
async searchWeather(city) {
this.showLoading();
for (let i = 0; i < this.apiEndpoints.length; i++) {
try {
const endpoint = this.apiEndpoints[this.currentEndpointIndex];
const response = await this.fetchWeatherData(endpoint, city);
if (response && response.code === 200) {
this.displayWeatherData(response.data);
return;
}
} catch (error) {
console.warn(`API ${this.apiEndpoints[this.currentEndpointIndex]} 请求失败:`, error);
}
// 切换到下一个API端点
this.currentEndpointIndex = (this.currentEndpointIndex + 1) % this.apiEndpoints.length;
}
// 所有API都失败了
this.showError('获取天气信息失败,请检查网络连接或稍后重试');
}
async fetchWeatherData(endpoint, city) {
const url = `${endpoint}?query=${encodeURIComponent(city)}`;
const response = await fetch(url, {
method: 'GET',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
timeout: 10000
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return await response.json();
}
displayWeatherData(data) {
const { location, daily_forecast, hourly_forecast } = data;
// 显示位置信息
document.getElementById('locationName').textContent = location.name || '未知位置';
document.getElementById('locationDetail').textContent =
`${location.province || ''} ${location.city || ''} ${location.county || ''}`.trim();
// 使用第一天的预报数据作为当前天气(今天的天气)
const todayWeather = daily_forecast && daily_forecast[0];
if (todayWeather) {
// 显示当前天气(使用今天的最高温度)
document.getElementById('temperature').textContent = todayWeather.max_temperature;
document.getElementById('weatherCondition').textContent =
`${todayWeather.day_condition}${todayWeather.night_condition}`;
// 体感温度(使用温度范围)
document.getElementById('feelsLike').textContent =
`温度范围 ${todayWeather.min_temperature}°C - ${todayWeather.max_temperature}°C`;
} else {
// 如果没有日预报数据,尝试使用小时预报数据
const currentHour = hourly_forecast && hourly_forecast[0];
if (currentHour) {
document.getElementById('temperature').textContent = currentHour.temperature;
document.getElementById('weatherCondition').textContent = currentHour.condition;
document.getElementById('feelsLike').textContent =
`风向: ${currentHour.wind_direction} ${currentHour.wind_power}`;
}
}
// 显示更新时间(使用当前时间)
document.getElementById('updateTime').textContent =
`${this.formatDate(new Date())} (基于预报数据)`;
// 显示天气预报
this.displayForecast(daily_forecast || []);
this.showWeatherContainer();
}
displayForecast(forecast) {
const forecastGrid = document.getElementById('forecastGrid');
forecastGrid.innerHTML = '';
if (!forecast || forecast.length === 0) {
forecastGrid.innerHTML = '<div class="no-forecast">暂无预报数据</div>';
return;
}
forecast.forEach((day, index) => {
const forecastItem = document.createElement('div');
forecastItem.className = 'forecast-item';
// 格式化日期显示
const dateStr = day.date || '';
const dateDesc = this.formatDateDesc(dateStr);
forecastItem.innerHTML = `
<div class="forecast-date">${dateDesc}</div>
<div class="forecast-weather">
<div class="weather-day">${day.day_condition || '未知'}</div>
<div class="weather-night">${day.night_condition || '未知'}</div>
</div>
<div class="forecast-temp">
<span class="temp-high">${day.max_temperature || '--'}°</span>
<span class="temp-low">${day.min_temperature || '--'}°</span>
</div>
<div class="forecast-wind">
<div>${day.day_wind_direction || ''} ${day.day_wind_power || ''}</div>
</div>
<div class="forecast-air">空气质量: ${day.air_quality || '未知'}</div>
`;
forecastGrid.appendChild(forecastItem);
});
}
// 华氏度转摄氏度
fahrenheitToCelsius(fahrenheit) {
const celsius = (fahrenheit - 32) * 5 / 9;
return Math.round(celsius * 10) / 10; // 保留一位小数
}
// 格式化时间
formatDate(date) {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
const seconds = String(date.getSeconds()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}
// 格式化日期描述
formatDateDesc(dateStr) {
if (!dateStr) return '未知日期';
try {
const date = new Date(dateStr);
const today = new Date();
const tomorrow = new Date(today);
tomorrow.setDate(today.getDate() + 1);
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
// 判断是今天、明天还是其他日期
if (date.toDateString() === today.toDateString()) {
return `今天 ${month}-${day}`;
} else if (date.toDateString() === tomorrow.toDateString()) {
return `明天 ${month}-${day}`;
} else {
const weekdays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
const weekday = weekdays[date.getDay()];
return `${weekday} ${month}-${day}`;
}
} catch (error) {
return dateStr;
}
}
showLoading() {
document.getElementById('loading').style.display = 'block';
document.getElementById('weatherContainer').style.display = 'none';
document.getElementById('errorMessage').style.display = 'none';
}
showWeatherContainer() {
document.getElementById('loading').style.display = 'none';
document.getElementById('weatherContainer').style.display = 'block';
document.getElementById('errorMessage').style.display = 'none';
}
showError(message) {
document.getElementById('loading').style.display = 'none';
document.getElementById('weatherContainer').style.display = 'none';
const errorElement = document.getElementById('errorMessage');
errorElement.style.display = 'block';
errorElement.querySelector('p').textContent = message;
}
}
// 页面加载完成后初始化应用
document.addEventListener('DOMContentLoaded', () => {
new WeatherApp();
});
// 添加页面可见性检测,当页面重新可见时刷新数据
document.addEventListener('visibilitychange', () => {
if (!document.hidden) {
const cityInput = document.getElementById('cityInput');
const city = cityInput.value.trim() || '北京';
// 延迟1秒刷新避免频繁请求
setTimeout(() => {
if (window.weatherApp) {
window.weatherApp.searchWeather(city);
}
}, 1000);
}
});
// 将应用实例暴露到全局,方便调试和其他功能调用
window.weatherApp = null;
document.addEventListener('DOMContentLoaded', () => {
window.weatherApp = new WeatherApp();
// 天气查询应用
class WeatherApp {
constructor() {
this.apiEndpoints = [
"https://60s.api.shumengya.top/v2/weather/forecast",
"https://60s-cf.viki.moe/v2/weather/forecast"
];
this.currentEndpointIndex = 0;
this.init();
}
init() {
this.bindEvents();
// 页面加载时自动查询北京天气
this.searchWeather('北京');
}
bindEvents() {
const searchBtn = document.getElementById('searchBtn');
const cityInput = document.getElementById('cityInput');
searchBtn.addEventListener('click', () => {
const city = cityInput.value.trim();
if (city) {
this.searchWeather(city);
} else {
this.showError('请输入城市名称');
}
});
cityInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
const city = cityInput.value.trim();
if (city) {
this.searchWeather(city);
} else {
this.showError('请输入城市名称');
}
}
});
// 防止输入框为空时查询
cityInput.addEventListener('input', () => {
const searchBtn = document.getElementById('searchBtn');
searchBtn.disabled = !cityInput.value.trim();
});
}
async searchWeather(city) {
this.showLoading();
for (let i = 0; i < this.apiEndpoints.length; i++) {
try {
const endpoint = this.apiEndpoints[this.currentEndpointIndex];
const response = await this.fetchWeatherData(endpoint, city);
if (response && response.code === 200) {
this.displayWeatherData(response.data);
return;
}
} catch (error) {
console.warn(`API ${this.apiEndpoints[this.currentEndpointIndex]} 请求失败:`, error);
}
// 切换到下一个API端点
this.currentEndpointIndex = (this.currentEndpointIndex + 1) % this.apiEndpoints.length;
}
// 所有API都失败了
this.showError('获取天气信息失败,请检查网络连接或稍后重试');
}
async fetchWeatherData(endpoint, city) {
const url = `${endpoint}?query=${encodeURIComponent(city)}`;
const response = await fetch(url, {
method: 'GET',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
timeout: 10000
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return await response.json();
}
displayWeatherData(data) {
const { location, daily_forecast, hourly_forecast } = data;
// 显示位置信息
document.getElementById('locationName').textContent = location.name || '未知位置';
document.getElementById('locationDetail').textContent =
`${location.province || ''} ${location.city || ''} ${location.county || ''}`.trim();
// 使用第一天的预报数据作为当前天气(今天的天气)
const todayWeather = daily_forecast && daily_forecast[0];
if (todayWeather) {
// 显示当前天气(使用今天的最高温度)
document.getElementById('temperature').textContent = todayWeather.max_temperature;
document.getElementById('weatherCondition').textContent =
`${todayWeather.day_condition}${todayWeather.night_condition}`;
// 体感温度(使用温度范围)
document.getElementById('feelsLike').textContent =
`温度范围 ${todayWeather.min_temperature}°C - ${todayWeather.max_temperature}°C`;
} else {
// 如果没有日预报数据,尝试使用小时预报数据
const currentHour = hourly_forecast && hourly_forecast[0];
if (currentHour) {
document.getElementById('temperature').textContent = currentHour.temperature;
document.getElementById('weatherCondition').textContent = currentHour.condition;
document.getElementById('feelsLike').textContent =
`风向: ${currentHour.wind_direction} ${currentHour.wind_power}`;
}
}
// 显示更新时间(使用当前时间)
document.getElementById('updateTime').textContent =
`${this.formatDate(new Date())} (基于预报数据)`;
// 显示天气预报
this.displayForecast(daily_forecast || []);
this.showWeatherContainer();
}
displayForecast(forecast) {
const forecastGrid = document.getElementById('forecastGrid');
forecastGrid.innerHTML = '';
if (!forecast || forecast.length === 0) {
forecastGrid.innerHTML = '<div class="no-forecast">暂无预报数据</div>';
return;
}
forecast.forEach((day, index) => {
const forecastItem = document.createElement('div');
forecastItem.className = 'forecast-item';
// 格式化日期显示
const dateStr = day.date || '';
const dateDesc = this.formatDateDesc(dateStr);
forecastItem.innerHTML = `
<div class="forecast-date">${dateDesc}</div>
<div class="forecast-weather">
<div class="weather-day">${day.day_condition || '未知'}</div>
<div class="weather-night">${day.night_condition || '未知'}</div>
</div>
<div class="forecast-temp">
<span class="temp-high">${day.max_temperature || '--'}°</span>
<span class="temp-low">${day.min_temperature || '--'}°</span>
</div>
<div class="forecast-wind">
<div>${day.day_wind_direction || ''} ${day.day_wind_power || ''}</div>
</div>
<div class="forecast-air">空气质量: ${day.air_quality || '未知'}</div>
`;
forecastGrid.appendChild(forecastItem);
});
}
// 华氏度转摄氏度
fahrenheitToCelsius(fahrenheit) {
const celsius = (fahrenheit - 32) * 5 / 9;
return Math.round(celsius * 10) / 10; // 保留一位小数
}
// 格式化时间
formatDate(date) {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
const seconds = String(date.getSeconds()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}
// 格式化日期描述
formatDateDesc(dateStr) {
if (!dateStr) return '未知日期';
try {
const date = new Date(dateStr);
const today = new Date();
const tomorrow = new Date(today);
tomorrow.setDate(today.getDate() + 1);
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
// 判断是今天、明天还是其他日期
if (date.toDateString() === today.toDateString()) {
return `今天 ${month}-${day}`;
} else if (date.toDateString() === tomorrow.toDateString()) {
return `明天 ${month}-${day}`;
} else {
const weekdays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
const weekday = weekdays[date.getDay()];
return `${weekday} ${month}-${day}`;
}
} catch (error) {
return dateStr;
}
}
showLoading() {
document.getElementById('loading').style.display = 'block';
document.getElementById('weatherContainer').style.display = 'none';
document.getElementById('errorMessage').style.display = 'none';
}
showWeatherContainer() {
document.getElementById('loading').style.display = 'none';
document.getElementById('weatherContainer').style.display = 'block';
document.getElementById('errorMessage').style.display = 'none';
}
showError(message) {
document.getElementById('loading').style.display = 'none';
document.getElementById('weatherContainer').style.display = 'none';
const errorElement = document.getElementById('errorMessage');
errorElement.style.display = 'block';
errorElement.querySelector('p').textContent = message;
}
}
// 页面加载完成后初始化应用
document.addEventListener('DOMContentLoaded', () => {
new WeatherApp();
});
// 添加页面可见性检测,当页面重新可见时刷新数据
document.addEventListener('visibilitychange', () => {
if (!document.hidden) {
const cityInput = document.getElementById('cityInput');
const city = cityInput.value.trim() || '北京';
// 延迟1秒刷新避免频繁请求
setTimeout(() => {
if (window.weatherApp) {
window.weatherApp.searchWeather(city);
}
}, 1000);
}
});
// 将应用实例暴露到全局,方便调试和其他功能调用
window.weatherApp = null;
document.addEventListener('DOMContentLoaded', () => {
window.weatherApp = new WeatherApp();
});

View File

@@ -1,202 +1,202 @@
/* 背景样式文件 - 独立管理背景相关CSS */
/* 主体背景 */
body {
background: linear-gradient(135deg, #a8e6cf 0%, #dcedc8 50%, #f0f4c3 100%);
background-attachment: fixed;
background-size: cover;
position: relative;
}
/* 背景装饰元素 */
body::before {
content: '';
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image:
radial-gradient(circle at 20% 80%, rgba(120, 219, 226, 0.1) 0%, transparent 50%),
radial-gradient(circle at 80% 20%, rgba(168, 230, 207, 0.1) 0%, transparent 50%),
radial-gradient(circle at 40% 40%, rgba(220, 237, 200, 0.1) 0%, transparent 50%);
pointer-events: none;
z-index: -1;
}
/* 动态背景效果 */
body::after {
content: '';
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background:
linear-gradient(45deg, transparent 30%, rgba(255, 255, 255, 0.05) 50%, transparent 70%),
linear-gradient(-45deg, transparent 30%, rgba(168, 230, 207, 0.05) 50%, transparent 70%);
background-size: 200px 200px;
animation: backgroundMove 20s linear infinite;
pointer-events: none;
z-index: -1;
}
/* 背景动画 */
@keyframes backgroundMove {
0% {
background-position: 0 0, 0 0;
}
100% {
background-position: 200px 200px, -200px -200px;
}
}
/* 容器背景 */
.container {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(5px);
border-radius: 20px;
border: 1px solid rgba(255, 255, 255, 0.2);
}
/* 卡片背景增强 */
.weather-card {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(15px);
border: 1px solid rgba(168, 230, 207, 0.3);
box-shadow:
0 10px 30px rgba(0, 0, 0, 0.1),
0 1px 8px rgba(168, 230, 207, 0.2),
inset 0 1px 0 rgba(255, 255, 255, 0.8);
}
/* 当前天气区域背景 */
.current-weather {
background: linear-gradient(135deg,
rgba(168, 230, 207, 0.8) 0%,
rgba(220, 237, 200, 0.8) 50%,
rgba(240, 244, 195, 0.8) 100%);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.3);
box-shadow:
0 4px 15px rgba(39, 174, 96, 0.1),
inset 0 1px 0 rgba(255, 255, 255, 0.6);
}
/* 详情项背景 */
.detail-item {
background: linear-gradient(135deg,
rgba(168, 230, 207, 0.1) 0%,
rgba(255, 255, 255, 0.1) 100%);
backdrop-filter: blur(5px);
border: 1px solid rgba(168, 230, 207, 0.2);
box-shadow: 0 2px 8px rgba(39, 174, 96, 0.05);
}
/* 生活指数项背景 */
.index-item {
background: linear-gradient(135deg,
rgba(168, 230, 207, 0.05) 0%,
rgba(255, 255, 255, 0.1) 100%);
backdrop-filter: blur(5px);
border: 1px solid rgba(168, 230, 207, 0.15);
box-shadow: 0 2px 10px rgba(39, 174, 96, 0.05);
}
.index-item:hover {
background: linear-gradient(135deg,
rgba(168, 230, 207, 0.1) 0%,
rgba(255, 255, 255, 0.15) 100%);
box-shadow: 0 5px 20px rgba(39, 174, 96, 0.1);
}
/* 输入框背景 */
#cityInput {
background: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(10px);
border: 2px solid rgba(168, 230, 207, 0.6);
box-shadow: 0 2px 10px rgba(39, 174, 96, 0.1);
}
#cityInput:focus {
background: rgba(255, 255, 255, 0.95);
box-shadow:
0 0 15px rgba(39, 174, 96, 0.2),
0 2px 10px rgba(39, 174, 96, 0.1);
}
/* 按钮背景 */
#searchBtn {
background: linear-gradient(135deg,
#27ae60 0%,
#2ecc71 50%,
#58d68d 100%);
box-shadow:
0 4px 15px rgba(39, 174, 96, 0.3),
inset 0 1px 0 rgba(255, 255, 255, 0.2);
}
#searchBtn:hover {
background: linear-gradient(135deg,
#229954 0%,
#27ae60 50%,
#52c370 100%);
box-shadow:
0 6px 20px rgba(39, 174, 96, 0.4),
inset 0 1px 0 rgba(255, 255, 255, 0.3);
}
/* 错误消息背景 */
.error-message {
background: linear-gradient(135deg,
rgba(231, 76, 60, 0.1) 0%,
rgba(255, 255, 255, 0.1) 100%);
backdrop-filter: blur(10px);
border: 1px solid rgba(231, 76, 60, 0.2);
box-shadow: 0 4px 15px rgba(231, 76, 60, 0.1);
}
/* 加载状态背景 */
.loading {
background: rgba(255, 255, 255, 0.8);
backdrop-filter: blur(10px);
border-radius: 15px;
border: 1px solid rgba(168, 230, 207, 0.3);
box-shadow: 0 4px 15px rgba(39, 174, 96, 0.1);
}
/* 移动端背景优化 */
@media (max-width: 767px) {
body::after {
background-size: 100px 100px;
animation-duration: 15s;
}
.container {
background: rgba(255, 255, 255, 0.05);
backdrop-filter: blur(3px);
}
.weather-card {
backdrop-filter: blur(10px);
}
}
/* 高性能设备背景增强 */
@media (min-width: 1024px) {
body::before {
background-image:
radial-gradient(circle at 20% 80%, rgba(120, 219, 226, 0.15) 0%, transparent 50%),
radial-gradient(circle at 80% 20%, rgba(168, 230, 207, 0.15) 0%, transparent 50%),
radial-gradient(circle at 40% 40%, rgba(220, 237, 200, 0.15) 0%, transparent 50%),
radial-gradient(circle at 60% 70%, rgba(240, 244, 195, 0.1) 0%, transparent 50%);
}
.weather-card {
backdrop-filter: blur(20px);
}
.current-weather {
backdrop-filter: blur(15px);
}
/* 背景样式文件 - 独立管理背景相关CSS */
/* 主体背景 */
body {
background: linear-gradient(135deg, #a8e6cf 0%, #dcedc8 50%, #f0f4c3 100%);
background-attachment: fixed;
background-size: cover;
position: relative;
}
/* 背景装饰元素 */
body::before {
content: '';
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image:
radial-gradient(circle at 20% 80%, rgba(120, 219, 226, 0.1) 0%, transparent 50%),
radial-gradient(circle at 80% 20%, rgba(168, 230, 207, 0.1) 0%, transparent 50%),
radial-gradient(circle at 40% 40%, rgba(220, 237, 200, 0.1) 0%, transparent 50%);
pointer-events: none;
z-index: -1;
}
/* 动态背景效果 */
body::after {
content: '';
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background:
linear-gradient(45deg, transparent 30%, rgba(255, 255, 255, 0.05) 50%, transparent 70%),
linear-gradient(-45deg, transparent 30%, rgba(168, 230, 207, 0.05) 50%, transparent 70%);
background-size: 200px 200px;
animation: backgroundMove 20s linear infinite;
pointer-events: none;
z-index: -1;
}
/* 背景动画 */
@keyframes backgroundMove {
0% {
background-position: 0 0, 0 0;
}
100% {
background-position: 200px 200px, -200px -200px;
}
}
/* 容器背景 */
.container {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(5px);
border-radius: 20px;
border: 1px solid rgba(255, 255, 255, 0.2);
}
/* 卡片背景增强 */
.weather-card {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(15px);
border: 1px solid rgba(168, 230, 207, 0.3);
box-shadow:
0 10px 30px rgba(0, 0, 0, 0.1),
0 1px 8px rgba(168, 230, 207, 0.2),
inset 0 1px 0 rgba(255, 255, 255, 0.8);
}
/* 当前天气区域背景 */
.current-weather {
background: linear-gradient(135deg,
rgba(168, 230, 207, 0.8) 0%,
rgba(220, 237, 200, 0.8) 50%,
rgba(240, 244, 195, 0.8) 100%);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.3);
box-shadow:
0 4px 15px rgba(39, 174, 96, 0.1),
inset 0 1px 0 rgba(255, 255, 255, 0.6);
}
/* 详情项背景 */
.detail-item {
background: linear-gradient(135deg,
rgba(168, 230, 207, 0.1) 0%,
rgba(255, 255, 255, 0.1) 100%);
backdrop-filter: blur(5px);
border: 1px solid rgba(168, 230, 207, 0.2);
box-shadow: 0 2px 8px rgba(39, 174, 96, 0.05);
}
/* 生活指数项背景 */
.index-item {
background: linear-gradient(135deg,
rgba(168, 230, 207, 0.05) 0%,
rgba(255, 255, 255, 0.1) 100%);
backdrop-filter: blur(5px);
border: 1px solid rgba(168, 230, 207, 0.15);
box-shadow: 0 2px 10px rgba(39, 174, 96, 0.05);
}
.index-item:hover {
background: linear-gradient(135deg,
rgba(168, 230, 207, 0.1) 0%,
rgba(255, 255, 255, 0.15) 100%);
box-shadow: 0 5px 20px rgba(39, 174, 96, 0.1);
}
/* 输入框背景 */
#cityInput {
background: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(10px);
border: 2px solid rgba(168, 230, 207, 0.6);
box-shadow: 0 2px 10px rgba(39, 174, 96, 0.1);
}
#cityInput:focus {
background: rgba(255, 255, 255, 0.95);
box-shadow:
0 0 15px rgba(39, 174, 96, 0.2),
0 2px 10px rgba(39, 174, 96, 0.1);
}
/* 按钮背景 */
#searchBtn {
background: linear-gradient(135deg,
#27ae60 0%,
#2ecc71 50%,
#58d68d 100%);
box-shadow:
0 4px 15px rgba(39, 174, 96, 0.3),
inset 0 1px 0 rgba(255, 255, 255, 0.2);
}
#searchBtn:hover {
background: linear-gradient(135deg,
#229954 0%,
#27ae60 50%,
#52c370 100%);
box-shadow:
0 6px 20px rgba(39, 174, 96, 0.4),
inset 0 1px 0 rgba(255, 255, 255, 0.3);
}
/* 错误消息背景 */
.error-message {
background: linear-gradient(135deg,
rgba(231, 76, 60, 0.1) 0%,
rgba(255, 255, 255, 0.1) 100%);
backdrop-filter: blur(10px);
border: 1px solid rgba(231, 76, 60, 0.2);
box-shadow: 0 4px 15px rgba(231, 76, 60, 0.1);
}
/* 加载状态背景 */
.loading {
background: rgba(255, 255, 255, 0.8);
backdrop-filter: blur(10px);
border-radius: 15px;
border: 1px solid rgba(168, 230, 207, 0.3);
box-shadow: 0 4px 15px rgba(39, 174, 96, 0.1);
}
/* 移动端背景优化 */
@media (max-width: 767px) {
body::after {
background-size: 100px 100px;
animation-duration: 15s;
}
.container {
background: rgba(255, 255, 255, 0.05);
backdrop-filter: blur(3px);
}
.weather-card {
backdrop-filter: blur(10px);
}
}
/* 高性能设备背景增强 */
@media (min-width: 1024px) {
body::before {
background-image:
radial-gradient(circle at 20% 80%, rgba(120, 219, 226, 0.15) 0%, transparent 50%),
radial-gradient(circle at 80% 20%, rgba(168, 230, 207, 0.15) 0%, transparent 50%),
radial-gradient(circle at 40% 40%, rgba(220, 237, 200, 0.15) 0%, transparent 50%),
radial-gradient(circle at 60% 70%, rgba(240, 244, 195, 0.1) 0%, transparent 50%);
}
.weather-card {
backdrop-filter: blur(20px);
}
.current-weather {
backdrop-filter: blur(15px);
}
}

View File

@@ -1,143 +1,143 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>实时天气查询</title>
<link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="background.css">
</head>
<body>
<div class="container">
<header class="header">
<h1>实时天气</h1>
<div class="search-box">
<input type="text" id="cityInput" placeholder="请输入城市名称..." value="北京">
<button id="searchBtn">查询</button>
</div>
</header>
<main class="main-content">
<div class="loading" id="loading">正在加载天气数据...</div>
<div class="weather-card" id="weatherCard" style="display: none;">
<div class="location-info">
<h2 id="locationName">北京</h2>
<p id="updateTime">更新时间: --</p>
</div>
<div class="current-weather">
<div class="temperature-section">
<span class="temperature" id="temperature">--°C</span>
<span class="weather-desc" id="weatherDesc">--</span>
</div>
<div class="weather-icon" id="weatherIcon">🌤️</div>
</div>
<div class="weather-details">
<div class="detail-item">
<span class="label">体感温度</span>
<span class="value" id="feelsLike">--°C</span>
</div>
<div class="detail-item">
<span class="label">湿度</span>
<span class="value" id="humidity">--%</span>
</div>
<div class="detail-item">
<span class="label">风向</span>
<span class="value" id="windDirection">--</span>
</div>
<div class="detail-item">
<span class="label">风力</span>
<span class="value" id="windStrength">--</span>
</div>
<div class="detail-item">
<span class="label">气压</span>
<span class="value" id="pressure">-- hPa</span>
</div>
<div class="detail-item">
<span class="label">能见度</span>
<span class="value" id="visibility">--</span>
</div>
<div class="detail-item">
<span class="label">空气质量</span>
<span class="value" id="aqi">AQI --</span>
</div>
<div class="detail-item">
<span class="label">PM2.5</span>
<span class="value" id="pm25">-- μg/m³</span>
</div>
</div>
<div class="life-index">
<h3>生活指数</h3>
<div class="index-grid">
<div class="index-item">
<div class="index-icon">🌡️</div>
<div class="index-content">
<div class="index-title">舒适度</div>
<div class="index-level" id="comfortLevel">--</div>
<div class="index-desc" id="comfortDesc">--</div>
</div>
</div>
<div class="index-item">
<div class="index-icon">👕</div>
<div class="index-content">
<div class="index-title">穿衣指数</div>
<div class="index-level" id="clothingLevel">--</div>
<div class="index-desc" id="clothingDesc">--</div>
</div>
</div>
<div class="index-item">
<div class="index-icon">☂️</div>
<div class="index-content">
<div class="index-title">雨伞指数</div>
<div class="index-level" id="umbrellaLevel">--</div>
<div class="index-desc" id="umbrellaDesc">--</div>
</div>
</div>
<div class="index-item">
<div class="index-icon">☀️</div>
<div class="index-content">
<div class="index-title">紫外线</div>
<div class="index-level" id="uvLevel">--</div>
<div class="index-desc" id="uvDesc">--</div>
</div>
</div>
<div class="index-item">
<div class="index-icon">🚗</div>
<div class="index-content">
<div class="index-title">洗车指数</div>
<div class="index-level" id="carWashLevel">--</div>
<div class="index-desc" id="carWashDesc">--</div>
</div>
</div>
<div class="index-item">
<div class="index-icon">🎒</div>
<div class="index-content">
<div class="index-title">旅游指数</div>
<div class="index-level" id="travelLevel">--</div>
<div class="index-desc" id="travelDesc">--</div>
</div>
</div>
<div class="index-item">
<div class="index-icon">🏃</div>
<div class="index-content">
<div class="index-title">运动指数</div>
<div class="index-level" id="sportLevel">--</div>
<div class="index-desc" id="sportDesc">--</div>
</div>
</div>
</div>
</div>
</div>
<div class="error-message" id="errorMessage" style="display: none;">
<p>获取天气数据失败,请检查网络连接或稍后重试</p>
</div>
</main>
</div>
<script src="script.js"></script>
</body>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>实时天气查询</title>
<link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="background.css">
</head>
<body>
<div class="container">
<header class="header">
<h1>实时天气</h1>
<div class="search-box">
<input type="text" id="cityInput" placeholder="请输入城市名称..." value="北京">
<button id="searchBtn">查询</button>
</div>
</header>
<main class="main-content">
<div class="loading" id="loading">正在加载天气数据...</div>
<div class="weather-card" id="weatherCard" style="display: none;">
<div class="location-info">
<h2 id="locationName">北京</h2>
<p id="updateTime">更新时间: --</p>
</div>
<div class="current-weather">
<div class="temperature-section">
<span class="temperature" id="temperature">--°C</span>
<span class="weather-desc" id="weatherDesc">--</span>
</div>
<div class="weather-icon" id="weatherIcon">🌤️</div>
</div>
<div class="weather-details">
<div class="detail-item">
<span class="label">体感温度</span>
<span class="value" id="feelsLike">--°C</span>
</div>
<div class="detail-item">
<span class="label">湿度</span>
<span class="value" id="humidity">--%</span>
</div>
<div class="detail-item">
<span class="label">风向</span>
<span class="value" id="windDirection">--</span>
</div>
<div class="detail-item">
<span class="label">风力</span>
<span class="value" id="windStrength">--</span>
</div>
<div class="detail-item">
<span class="label">气压</span>
<span class="value" id="pressure">-- hPa</span>
</div>
<div class="detail-item">
<span class="label">能见度</span>
<span class="value" id="visibility">--</span>
</div>
<div class="detail-item">
<span class="label">空气质量</span>
<span class="value" id="aqi">AQI --</span>
</div>
<div class="detail-item">
<span class="label">PM2.5</span>
<span class="value" id="pm25">-- μg/m³</span>
</div>
</div>
<div class="life-index">
<h3>生活指数</h3>
<div class="index-grid">
<div class="index-item">
<div class="index-icon">🌡️</div>
<div class="index-content">
<div class="index-title">舒适度</div>
<div class="index-level" id="comfortLevel">--</div>
<div class="index-desc" id="comfortDesc">--</div>
</div>
</div>
<div class="index-item">
<div class="index-icon">👕</div>
<div class="index-content">
<div class="index-title">穿衣指数</div>
<div class="index-level" id="clothingLevel">--</div>
<div class="index-desc" id="clothingDesc">--</div>
</div>
</div>
<div class="index-item">
<div class="index-icon">☂️</div>
<div class="index-content">
<div class="index-title">雨伞指数</div>
<div class="index-level" id="umbrellaLevel">--</div>
<div class="index-desc" id="umbrellaDesc">--</div>
</div>
</div>
<div class="index-item">
<div class="index-icon">☀️</div>
<div class="index-content">
<div class="index-title">紫外线</div>
<div class="index-level" id="uvLevel">--</div>
<div class="index-desc" id="uvDesc">--</div>
</div>
</div>
<div class="index-item">
<div class="index-icon">🚗</div>
<div class="index-content">
<div class="index-title">洗车指数</div>
<div class="index-level" id="carWashLevel">--</div>
<div class="index-desc" id="carWashDesc">--</div>
</div>
</div>
<div class="index-item">
<div class="index-icon">🎒</div>
<div class="index-content">
<div class="index-title">旅游指数</div>
<div class="index-level" id="travelLevel">--</div>
<div class="index-desc" id="travelDesc">--</div>
</div>
</div>
<div class="index-item">
<div class="index-icon">🏃</div>
<div class="index-content">
<div class="index-title">运动指数</div>
<div class="index-level" id="sportLevel">--</div>
<div class="index-desc" id="sportDesc">--</div>
</div>
</div>
</div>
</div>
</div>
<div class="error-message" id="errorMessage" style="display: none;">
<p>获取天气数据失败,请检查网络连接或稍后重试</p>
</div>
</main>
</div>
<script src="script.js"></script>
</body>
</html>

View File

@@ -1,354 +1,354 @@
// 天气应用主要功能
class WeatherApp {
constructor() {
this.apiUrl = 'https://60s.api.shumengya.top/v2/weather';
this.init();
}
init() {
this.bindEvents();
this.loadWeather('北京'); // 默认加载北京天气
}
bindEvents() {
const searchBtn = document.getElementById('searchBtn');
const cityInput = document.getElementById('cityInput');
// 搜索按钮点击事件
searchBtn.addEventListener('click', () => {
const city = cityInput.value.trim();
if (city) {
this.loadWeather(city);
}
});
// 输入框回车事件
cityInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
const city = cityInput.value.trim();
if (city) {
this.loadWeather(city);
}
}
});
}
async loadWeather(city) {
this.showLoading();
try {
const response = await fetch(`${this.apiUrl}?query=${encodeURIComponent(city)}`);
if (!response.ok) {
throw new Error(`HTTP错误: ${response.status}`);
}
const data = await response.json();
console.log('完整API响应:', data); // 调试日志
if (data.code === 200 && data.data) {
this.displayWeather(data.data);
this.hideLoading();
} else {
throw new Error(data.message || `API返回错误: code=${data.code}`);
}
} catch (error) {
console.error('获取天气数据失败:', error);
console.error('错误详情:', {
message: error.message,
stack: error.stack
});
this.showError(error.message);
this.hideLoading();
}
}
showLoading() {
document.getElementById('loading').style.display = 'block';
document.getElementById('weatherCard').style.display = 'none';
document.getElementById('errorMessage').style.display = 'none';
}
hideLoading() {
document.getElementById('loading').style.display = 'none';
}
showError(message = '获取天气数据失败,请检查网络连接或稍后重试') {
const errorElement = document.getElementById('errorMessage');
const errorText = errorElement.querySelector('p');
if (errorText) {
errorText.textContent = message;
}
errorElement.style.display = 'block';
document.getElementById('weatherCard').style.display = 'none';
}
displayWeather(data) {
console.log('API返回数据:', data); // 调试日志
// 根据实际API结构解构数据
const location = data.location || {};
const realtime = data.realtime || {};
const air_quality = realtime.air_quality || {};
const life_indices = realtime.life_indices || [];
// 显示位置信息
const locationName = location.formatted || location.city || location.name || '未知位置';
document.getElementById('locationName').textContent = locationName;
const updateTime = realtime.updated || '未知时间';
document.getElementById('updateTime').textContent = `更新时间: ${updateTime}`;
// 显示当前天气
const temperature = realtime.temperature !== undefined ? realtime.temperature : '--';
document.getElementById('temperature').textContent = `${temperature}°C`;
const condition = realtime.weather || realtime.weather_desc || '未知';
document.getElementById('weatherDesc').textContent = condition;
document.getElementById('weatherIcon').textContent = this.getWeatherIcon(condition);
// 显示天气详情
const feelsLike = realtime.temperature_feels_like !== undefined ? realtime.temperature_feels_like : temperature;
document.getElementById('feelsLike').textContent = `${feelsLike}°C`;
const humidity = realtime.humidity !== undefined ? realtime.humidity : '--';
document.getElementById('humidity').textContent = `${humidity}%`;
const windDirection = realtime.wind_direction || '--';
document.getElementById('windDirection').textContent = windDirection;
const windPower = realtime.wind_power || realtime.wind_strength || '--';
document.getElementById('windStrength').textContent = windPower;
const pressure = realtime.pressure !== undefined ? realtime.pressure : '--';
document.getElementById('pressure').textContent = `${pressure} hPa`;
document.getElementById('visibility').textContent = '--'; // API中没有能见度数据
const aqi = air_quality.aqi !== undefined ? air_quality.aqi : '--';
document.getElementById('aqi').textContent = `AQI ${aqi}`;
const pm25 = air_quality.pm25 !== undefined ? air_quality.pm25 : '--';
document.getElementById('pm25').textContent = `${pm25} μg/m³`;
// 显示生活指数
if (life_indices && life_indices.length > 0) {
this.displayLifeIndex(life_indices);
} else {
// 如果没有生活指数数据,重置显示
this.resetLifeIndex();
}
// 显示天气卡片
document.getElementById('weatherCard').style.display = 'block';
}
displayLifeIndex(lifeIndices) {
const indexMap = {
comfort: { level: 'comfortLevel', desc: 'comfortDesc' },
clothes: { level: 'clothingLevel', desc: 'clothingDesc' },
umbrella: { level: 'umbrellaLevel', desc: 'umbrellaDesc' },
ultraviolet: { level: 'uvLevel', desc: 'uvDesc' },
carwash: { level: 'carWashLevel', desc: 'carWashDesc' },
tourism: { level: 'travelLevel', desc: 'travelDesc' },
sports: { level: 'sportLevel', desc: 'sportDesc' }
};
// 重置所有指数显示
this.resetLifeIndex();
// 根据新的API数据结构更新生活指数
if (Array.isArray(lifeIndices)) {
lifeIndices.forEach(index => {
if (index && index.key && indexMap[index.key]) {
const { level, desc } = indexMap[index.key];
const levelElement = document.getElementById(level);
const descElement = document.getElementById(desc);
if (levelElement) levelElement.textContent = index.level || '--';
if (descElement) descElement.textContent = index.description || '--';
}
});
}
}
resetLifeIndex() {
const indexMap = {
comfort: { level: 'comfortLevel', desc: 'comfortDesc' },
clothes: { level: 'clothingLevel', desc: 'clothingDesc' },
umbrella: { level: 'umbrellaLevel', desc: 'umbrellaDesc' },
ultraviolet: { level: 'uvLevel', desc: 'uvDesc' },
carwash: { level: 'carWashLevel', desc: 'carWashDesc' },
tourism: { level: 'travelLevel', desc: 'travelDesc' },
sports: { level: 'sportLevel', desc: 'sportDesc' }
};
Object.values(indexMap).forEach(({ level, desc }) => {
const levelElement = document.getElementById(level);
const descElement = document.getElementById(desc);
if (levelElement) levelElement.textContent = '--';
if (descElement) descElement.textContent = '--';
});
}
getWeatherIcon(weather) {
const iconMap = {
'晴': '☀️',
'多云': '⛅',
'阴': '☁️',
'小雨': '🌦️',
'中雨': '🌧️',
'大雨': '⛈️',
'雷阵雨': '⛈️',
'雪': '❄️',
'小雪': '🌨️',
'中雪': '❄️',
'大雪': '❄️',
'雾': '🌫️',
'霾': '😷',
'沙尘暴': '🌪️'
};
// 查找匹配的天气图标
for (const [key, icon] of Object.entries(iconMap)) {
if (weather.includes(key)) {
return icon;
}
}
// 默认图标
return '🌤️';
}
// 获取空气质量等级颜色
getAQIColor(aqi) {
if (aqi <= 50) return '#00e400';
if (aqi <= 100) return '#ffff00';
if (aqi <= 150) return '#ff7e00';
if (aqi <= 200) return '#ff0000';
if (aqi <= 300) return '#8f3f97';
return '#7e0023';
}
// 格式化时间
formatTime(timeString) {
try {
const date = new Date(timeString);
return date.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit'
});
} catch (error) {
return timeString;
}
}
}
// 页面加载完成后初始化应用
document.addEventListener('DOMContentLoaded', () => {
new WeatherApp();
});
// 添加一些实用的工具函数
const utils = {
// 防抖函数
debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
},
// 节流函数
throttle(func, limit) {
let inThrottle;
return function() {
const args = arguments;
const context = this;
if (!inThrottle) {
func.apply(context, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
},
// 检查网络状态
checkNetworkStatus() {
return navigator.onLine;
},
// 显示提示消息
showToast(message, type = 'info') {
const toast = document.createElement('div');
toast.className = `toast toast-${type}`;
toast.textContent = message;
toast.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
padding: 12px 20px;
background: ${type === 'error' ? '#e74c3c' : '#27ae60'};
color: white;
border-radius: 8px;
z-index: 1000;
animation: slideIn 0.3s ease;
`;
document.body.appendChild(toast);
setTimeout(() => {
toast.style.animation = 'slideOut 0.3s ease';
setTimeout(() => {
document.body.removeChild(toast);
}, 300);
}, 3000);
}
};
// 添加CSS动画
const style = document.createElement('style');
style.textContent = `
@keyframes slideIn {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
@keyframes slideOut {
from {
transform: translateX(0);
opacity: 1;
}
to {
transform: translateX(100%);
opacity: 0;
}
}
`;
document.head.appendChild(style);
// 网络状态监听
window.addEventListener('online', () => {
utils.showToast('网络连接已恢复', 'success');
});
window.addEventListener('offline', () => {
utils.showToast('网络连接已断开', 'error');
// 天气应用主要功能
class WeatherApp {
constructor() {
this.apiUrl = 'https://60s.api.shumengya.top/v2/weather';
this.init();
}
init() {
this.bindEvents();
this.loadWeather('北京'); // 默认加载北京天气
}
bindEvents() {
const searchBtn = document.getElementById('searchBtn');
const cityInput = document.getElementById('cityInput');
// 搜索按钮点击事件
searchBtn.addEventListener('click', () => {
const city = cityInput.value.trim();
if (city) {
this.loadWeather(city);
}
});
// 输入框回车事件
cityInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
const city = cityInput.value.trim();
if (city) {
this.loadWeather(city);
}
}
});
}
async loadWeather(city) {
this.showLoading();
try {
const response = await fetch(`${this.apiUrl}?query=${encodeURIComponent(city)}`);
if (!response.ok) {
throw new Error(`HTTP错误: ${response.status}`);
}
const data = await response.json();
console.log('完整API响应:', data); // 调试日志
if (data.code === 200 && data.data) {
this.displayWeather(data.data);
this.hideLoading();
} else {
throw new Error(data.message || `API返回错误: code=${data.code}`);
}
} catch (error) {
console.error('获取天气数据失败:', error);
console.error('错误详情:', {
message: error.message,
stack: error.stack
});
this.showError(error.message);
this.hideLoading();
}
}
showLoading() {
document.getElementById('loading').style.display = 'block';
document.getElementById('weatherCard').style.display = 'none';
document.getElementById('errorMessage').style.display = 'none';
}
hideLoading() {
document.getElementById('loading').style.display = 'none';
}
showError(message = '获取天气数据失败,请检查网络连接或稍后重试') {
const errorElement = document.getElementById('errorMessage');
const errorText = errorElement.querySelector('p');
if (errorText) {
errorText.textContent = message;
}
errorElement.style.display = 'block';
document.getElementById('weatherCard').style.display = 'none';
}
displayWeather(data) {
console.log('API返回数据:', data); // 调试日志
// 根据实际API结构解构数据
const location = data.location || {};
const realtime = data.realtime || {};
const air_quality = realtime.air_quality || {};
const life_indices = realtime.life_indices || [];
// 显示位置信息
const locationName = location.formatted || location.city || location.name || '未知位置';
document.getElementById('locationName').textContent = locationName;
const updateTime = realtime.updated || '未知时间';
document.getElementById('updateTime').textContent = `更新时间: ${updateTime}`;
// 显示当前天气
const temperature = realtime.temperature !== undefined ? realtime.temperature : '--';
document.getElementById('temperature').textContent = `${temperature}°C`;
const condition = realtime.weather || realtime.weather_desc || '未知';
document.getElementById('weatherDesc').textContent = condition;
document.getElementById('weatherIcon').textContent = this.getWeatherIcon(condition);
// 显示天气详情
const feelsLike = realtime.temperature_feels_like !== undefined ? realtime.temperature_feels_like : temperature;
document.getElementById('feelsLike').textContent = `${feelsLike}°C`;
const humidity = realtime.humidity !== undefined ? realtime.humidity : '--';
document.getElementById('humidity').textContent = `${humidity}%`;
const windDirection = realtime.wind_direction || '--';
document.getElementById('windDirection').textContent = windDirection;
const windPower = realtime.wind_power || realtime.wind_strength || '--';
document.getElementById('windStrength').textContent = windPower;
const pressure = realtime.pressure !== undefined ? realtime.pressure : '--';
document.getElementById('pressure').textContent = `${pressure} hPa`;
document.getElementById('visibility').textContent = '--'; // API中没有能见度数据
const aqi = air_quality.aqi !== undefined ? air_quality.aqi : '--';
document.getElementById('aqi').textContent = `AQI ${aqi}`;
const pm25 = air_quality.pm25 !== undefined ? air_quality.pm25 : '--';
document.getElementById('pm25').textContent = `${pm25} μg/m³`;
// 显示生活指数
if (life_indices && life_indices.length > 0) {
this.displayLifeIndex(life_indices);
} else {
// 如果没有生活指数数据,重置显示
this.resetLifeIndex();
}
// 显示天气卡片
document.getElementById('weatherCard').style.display = 'block';
}
displayLifeIndex(lifeIndices) {
const indexMap = {
comfort: { level: 'comfortLevel', desc: 'comfortDesc' },
clothes: { level: 'clothingLevel', desc: 'clothingDesc' },
umbrella: { level: 'umbrellaLevel', desc: 'umbrellaDesc' },
ultraviolet: { level: 'uvLevel', desc: 'uvDesc' },
carwash: { level: 'carWashLevel', desc: 'carWashDesc' },
tourism: { level: 'travelLevel', desc: 'travelDesc' },
sports: { level: 'sportLevel', desc: 'sportDesc' }
};
// 重置所有指数显示
this.resetLifeIndex();
// 根据新的API数据结构更新生活指数
if (Array.isArray(lifeIndices)) {
lifeIndices.forEach(index => {
if (index && index.key && indexMap[index.key]) {
const { level, desc } = indexMap[index.key];
const levelElement = document.getElementById(level);
const descElement = document.getElementById(desc);
if (levelElement) levelElement.textContent = index.level || '--';
if (descElement) descElement.textContent = index.description || '--';
}
});
}
}
resetLifeIndex() {
const indexMap = {
comfort: { level: 'comfortLevel', desc: 'comfortDesc' },
clothes: { level: 'clothingLevel', desc: 'clothingDesc' },
umbrella: { level: 'umbrellaLevel', desc: 'umbrellaDesc' },
ultraviolet: { level: 'uvLevel', desc: 'uvDesc' },
carwash: { level: 'carWashLevel', desc: 'carWashDesc' },
tourism: { level: 'travelLevel', desc: 'travelDesc' },
sports: { level: 'sportLevel', desc: 'sportDesc' }
};
Object.values(indexMap).forEach(({ level, desc }) => {
const levelElement = document.getElementById(level);
const descElement = document.getElementById(desc);
if (levelElement) levelElement.textContent = '--';
if (descElement) descElement.textContent = '--';
});
}
getWeatherIcon(weather) {
const iconMap = {
'晴': '☀️',
'多云': '⛅',
'阴': '☁️',
'小雨': '🌦️',
'中雨': '🌧️',
'大雨': '⛈️',
'雷阵雨': '⛈️',
'雪': '❄️',
'小雪': '🌨️',
'中雪': '❄️',
'大雪': '❄️',
'雾': '🌫️',
'霾': '😷',
'沙尘暴': '🌪️'
};
// 查找匹配的天气图标
for (const [key, icon] of Object.entries(iconMap)) {
if (weather.includes(key)) {
return icon;
}
}
// 默认图标
return '🌤️';
}
// 获取空气质量等级颜色
getAQIColor(aqi) {
if (aqi <= 50) return '#00e400';
if (aqi <= 100) return '#ffff00';
if (aqi <= 150) return '#ff7e00';
if (aqi <= 200) return '#ff0000';
if (aqi <= 300) return '#8f3f97';
return '#7e0023';
}
// 格式化时间
formatTime(timeString) {
try {
const date = new Date(timeString);
return date.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit'
});
} catch (error) {
return timeString;
}
}
}
// 页面加载完成后初始化应用
document.addEventListener('DOMContentLoaded', () => {
new WeatherApp();
});
// 添加一些实用的工具函数
const utils = {
// 防抖函数
debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
},
// 节流函数
throttle(func, limit) {
let inThrottle;
return function() {
const args = arguments;
const context = this;
if (!inThrottle) {
func.apply(context, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
},
// 检查网络状态
checkNetworkStatus() {
return navigator.onLine;
},
// 显示提示消息
showToast(message, type = 'info') {
const toast = document.createElement('div');
toast.className = `toast toast-${type}`;
toast.textContent = message;
toast.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
padding: 12px 20px;
background: ${type === 'error' ? '#e74c3c' : '#27ae60'};
color: white;
border-radius: 8px;
z-index: 1000;
animation: slideIn 0.3s ease;
`;
document.body.appendChild(toast);
setTimeout(() => {
toast.style.animation = 'slideOut 0.3s ease';
setTimeout(() => {
document.body.removeChild(toast);
}, 300);
}, 3000);
}
};
// 添加CSS动画
const style = document.createElement('style');
style.textContent = `
@keyframes slideIn {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
@keyframes slideOut {
from {
transform: translateX(0);
opacity: 1;
}
to {
transform: translateX(100%);
opacity: 0;
}
}
`;
document.head.appendChild(style);
// 网络状态监听
window.addEventListener('online', () => {
utils.showToast('网络连接已恢复', 'success');
});
window.addEventListener('offline', () => {
utils.showToast('网络连接已断开', 'error');
});

View File

@@ -1,442 +1,442 @@
/* 基础样式重置 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Microsoft YaHei', 'PingFang SC', 'Helvetica Neue', Arial, sans-serif;
line-height: 1.6;
color: #2c3e50;
min-height: 100vh;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
min-height: 100vh;
}
/* 头部样式 */
.header {
text-align: center;
margin-bottom: 30px;
}
.header h1 {
font-size: 2.5rem;
color: #27ae60;
margin-bottom: 20px;
font-weight: 300;
text-shadow: 0 2px 4px rgba(39, 174, 96, 0.1);
}
.search-box {
display: flex;
justify-content: center;
gap: 10px;
margin-bottom: 20px;
}
#cityInput {
padding: 12px 16px;
border: 2px solid #a8e6cf;
border-radius: 25px;
font-size: 16px;
outline: none;
background: rgba(255, 255, 255, 0.9);
transition: all 0.3s ease;
min-width: 200px;
}
#cityInput:focus {
border-color: #27ae60;
box-shadow: 0 0 10px rgba(39, 174, 96, 0.2);
}
#searchBtn {
padding: 12px 24px;
background: linear-gradient(135deg, #27ae60, #2ecc71);
color: white;
border: none;
border-radius: 25px;
font-size: 16px;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 4px 15px rgba(39, 174, 96, 0.3);
}
#searchBtn:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(39, 174, 96, 0.4);
}
#searchBtn:active {
transform: translateY(0);
}
/* 主要内容区域 */
.main-content {
display: flex;
justify-content: center;
align-items: flex-start;
}
.loading {
text-align: center;
font-size: 18px;
color: #27ae60;
padding: 40px;
}
.weather-card {
background: rgba(255, 255, 255, 0.95);
border-radius: 20px;
padding: 30px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
backdrop-filter: blur(10px);
border: 1px solid rgba(168, 230, 207, 0.3);
width: 100%;
max-width: 800px;
}
/* 位置信息 */
.location-info {
text-align: center;
margin-bottom: 30px;
padding-bottom: 20px;
border-bottom: 2px solid #a8e6cf;
}
.location-info h2 {
font-size: 2rem;
color: #27ae60;
margin-bottom: 10px;
}
.location-info p {
color: #7f8c8d;
font-size: 14px;
}
/* 当前天气 */
.current-weather {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30px;
padding: 20px;
background: linear-gradient(135deg, #a8e6cf, #dcedc8);
border-radius: 15px;
}
.temperature-section {
display: flex;
flex-direction: column;
align-items: flex-start;
}
.temperature {
font-size: 3.5rem;
font-weight: 300;
color: #27ae60;
line-height: 1;
}
.weather-desc {
font-size: 1.2rem;
color: #2c3e50;
margin-top: 5px;
}
.weather-icon {
font-size: 4rem;
opacity: 0.8;
}
/* 天气详情 */
.weather-details {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 15px;
margin-bottom: 30px;
}
.detail-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px;
background: rgba(168, 230, 207, 0.1);
border-radius: 10px;
border-left: 4px solid #27ae60;
}
.detail-item .label {
color: #7f8c8d;
font-size: 14px;
}
.detail-item .value {
color: #2c3e50;
font-weight: 500;
font-size: 16px;
}
/* 生活指数 */
.life-index h3 {
color: #27ae60;
margin-bottom: 20px;
font-size: 1.5rem;
text-align: center;
}
.index-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
}
.index-item {
display: flex;
align-items: flex-start;
padding: 20px;
background: rgba(168, 230, 207, 0.05);
border-radius: 15px;
border: 1px solid rgba(168, 230, 207, 0.2);
transition: all 0.3s ease;
}
.index-item:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(39, 174, 96, 0.1);
}
.index-icon {
font-size: 2rem;
margin-right: 15px;
opacity: 0.8;
}
.index-content {
flex: 1;
}
.index-title {
font-weight: 500;
color: #2c3e50;
margin-bottom: 5px;
}
.index-level {
color: #27ae60;
font-weight: 600;
margin-bottom: 5px;
}
.index-desc {
color: #7f8c8d;
font-size: 14px;
line-height: 1.4;
}
.error-message {
text-align: center;
padding: 40px;
color: #e74c3c;
background: rgba(231, 76, 60, 0.1);
border-radius: 15px;
border: 1px solid rgba(231, 76, 60, 0.2);
}
/* 平板端适配 (768px - 1024px) */
@media (min-width: 768px) and (max-width: 1024px) {
.container {
padding: 25px;
}
.header h1 {
font-size: 2.8rem;
}
.search-box {
max-width: 500px;
margin: 0 auto 20px;
}
.weather-details {
grid-template-columns: repeat(2, 1fr);
}
.index-grid {
grid-template-columns: repeat(2, 1fr);
}
.current-weather {
padding: 25px;
}
.temperature {
font-size: 4rem;
}
}
/* 电脑端适配 (1024px+) */
@media (min-width: 1024px) {
.container {
padding: 40px;
}
.header h1 {
font-size: 3.2rem;
}
.search-box {
max-width: 600px;
margin: 0 auto 30px;
}
.weather-card {
padding: 40px;
}
.weather-details {
grid-template-columns: repeat(4, 1fr);
}
.index-grid {
grid-template-columns: repeat(3, 1fr);
}
.current-weather {
padding: 30px;
}
.temperature {
font-size: 4.5rem;
}
.index-item {
padding: 25px;
}
}
/* 手机端适配 (优先优化) */
@media (max-width: 767px) {
.container {
padding: 15px;
}
.header h1 {
font-size: 2rem;
margin-bottom: 15px;
}
.search-box {
flex-direction: column;
align-items: center;
gap: 15px;
}
#cityInput {
width: 100%;
max-width: 300px;
font-size: 16px;
}
#searchBtn {
width: 100%;
max-width: 300px;
padding: 14px 24px;
}
.weather-card {
padding: 20px;
margin: 0;
}
.current-weather {
flex-direction: column;
text-align: center;
gap: 20px;
padding: 20px;
}
.temperature {
font-size: 3rem;
}
.weather-icon {
font-size: 3rem;
}
.weather-details {
grid-template-columns: 1fr;
gap: 10px;
}
.detail-item {
padding: 12px;
}
.index-grid {
grid-template-columns: 1fr;
gap: 15px;
}
.index-item {
padding: 15px;
}
.index-icon {
font-size: 1.5rem;
margin-right: 10px;
}
.life-index h3 {
font-size: 1.3rem;
margin-bottom: 15px;
}
.location-info h2 {
font-size: 1.5rem;
}
}
/* 超小屏幕适配 */
@media (max-width: 480px) {
.container {
padding: 10px;
}
.header h1 {
font-size: 1.8rem;
}
.weather-card {
padding: 15px;
}
.temperature {
font-size: 2.5rem;
}
.current-weather {
padding: 15px;
}
.detail-item {
padding: 10px;
font-size: 14px;
}
.index-item {
padding: 12px;
}
.index-desc {
font-size: 13px;
}
/* 基础样式重置 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Microsoft YaHei', 'PingFang SC', 'Helvetica Neue', Arial, sans-serif;
line-height: 1.6;
color: #2c3e50;
min-height: 100vh;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
min-height: 100vh;
}
/* 头部样式 */
.header {
text-align: center;
margin-bottom: 30px;
}
.header h1 {
font-size: 2.5rem;
color: #27ae60;
margin-bottom: 20px;
font-weight: 300;
text-shadow: 0 2px 4px rgba(39, 174, 96, 0.1);
}
.search-box {
display: flex;
justify-content: center;
gap: 10px;
margin-bottom: 20px;
}
#cityInput {
padding: 12px 16px;
border: 2px solid #a8e6cf;
border-radius: 25px;
font-size: 16px;
outline: none;
background: rgba(255, 255, 255, 0.9);
transition: all 0.3s ease;
min-width: 200px;
}
#cityInput:focus {
border-color: #27ae60;
box-shadow: 0 0 10px rgba(39, 174, 96, 0.2);
}
#searchBtn {
padding: 12px 24px;
background: linear-gradient(135deg, #27ae60, #2ecc71);
color: white;
border: none;
border-radius: 25px;
font-size: 16px;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 4px 15px rgba(39, 174, 96, 0.3);
}
#searchBtn:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(39, 174, 96, 0.4);
}
#searchBtn:active {
transform: translateY(0);
}
/* 主要内容区域 */
.main-content {
display: flex;
justify-content: center;
align-items: flex-start;
}
.loading {
text-align: center;
font-size: 18px;
color: #27ae60;
padding: 40px;
}
.weather-card {
background: rgba(255, 255, 255, 0.95);
border-radius: 20px;
padding: 30px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
backdrop-filter: blur(10px);
border: 1px solid rgba(168, 230, 207, 0.3);
width: 100%;
max-width: 800px;
}
/* 位置信息 */
.location-info {
text-align: center;
margin-bottom: 30px;
padding-bottom: 20px;
border-bottom: 2px solid #a8e6cf;
}
.location-info h2 {
font-size: 2rem;
color: #27ae60;
margin-bottom: 10px;
}
.location-info p {
color: #7f8c8d;
font-size: 14px;
}
/* 当前天气 */
.current-weather {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30px;
padding: 20px;
background: linear-gradient(135deg, #a8e6cf, #dcedc8);
border-radius: 15px;
}
.temperature-section {
display: flex;
flex-direction: column;
align-items: flex-start;
}
.temperature {
font-size: 3.5rem;
font-weight: 300;
color: #27ae60;
line-height: 1;
}
.weather-desc {
font-size: 1.2rem;
color: #2c3e50;
margin-top: 5px;
}
.weather-icon {
font-size: 4rem;
opacity: 0.8;
}
/* 天气详情 */
.weather-details {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 15px;
margin-bottom: 30px;
}
.detail-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px;
background: rgba(168, 230, 207, 0.1);
border-radius: 10px;
border-left: 4px solid #27ae60;
}
.detail-item .label {
color: #7f8c8d;
font-size: 14px;
}
.detail-item .value {
color: #2c3e50;
font-weight: 500;
font-size: 16px;
}
/* 生活指数 */
.life-index h3 {
color: #27ae60;
margin-bottom: 20px;
font-size: 1.5rem;
text-align: center;
}
.index-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
}
.index-item {
display: flex;
align-items: flex-start;
padding: 20px;
background: rgba(168, 230, 207, 0.05);
border-radius: 15px;
border: 1px solid rgba(168, 230, 207, 0.2);
transition: all 0.3s ease;
}
.index-item:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(39, 174, 96, 0.1);
}
.index-icon {
font-size: 2rem;
margin-right: 15px;
opacity: 0.8;
}
.index-content {
flex: 1;
}
.index-title {
font-weight: 500;
color: #2c3e50;
margin-bottom: 5px;
}
.index-level {
color: #27ae60;
font-weight: 600;
margin-bottom: 5px;
}
.index-desc {
color: #7f8c8d;
font-size: 14px;
line-height: 1.4;
}
.error-message {
text-align: center;
padding: 40px;
color: #e74c3c;
background: rgba(231, 76, 60, 0.1);
border-radius: 15px;
border: 1px solid rgba(231, 76, 60, 0.2);
}
/* 平板端适配 (768px - 1024px) */
@media (min-width: 768px) and (max-width: 1024px) {
.container {
padding: 25px;
}
.header h1 {
font-size: 2.8rem;
}
.search-box {
max-width: 500px;
margin: 0 auto 20px;
}
.weather-details {
grid-template-columns: repeat(2, 1fr);
}
.index-grid {
grid-template-columns: repeat(2, 1fr);
}
.current-weather {
padding: 25px;
}
.temperature {
font-size: 4rem;
}
}
/* 电脑端适配 (1024px+) */
@media (min-width: 1024px) {
.container {
padding: 40px;
}
.header h1 {
font-size: 3.2rem;
}
.search-box {
max-width: 600px;
margin: 0 auto 30px;
}
.weather-card {
padding: 40px;
}
.weather-details {
grid-template-columns: repeat(4, 1fr);
}
.index-grid {
grid-template-columns: repeat(3, 1fr);
}
.current-weather {
padding: 30px;
}
.temperature {
font-size: 4.5rem;
}
.index-item {
padding: 25px;
}
}
/* 手机端适配 (优先优化) */
@media (max-width: 767px) {
.container {
padding: 15px;
}
.header h1 {
font-size: 2rem;
margin-bottom: 15px;
}
.search-box {
flex-direction: column;
align-items: center;
gap: 15px;
}
#cityInput {
width: 100%;
max-width: 300px;
font-size: 16px;
}
#searchBtn {
width: 100%;
max-width: 300px;
padding: 14px 24px;
}
.weather-card {
padding: 20px;
margin: 0;
}
.current-weather {
flex-direction: column;
text-align: center;
gap: 20px;
padding: 20px;
}
.temperature {
font-size: 3rem;
}
.weather-icon {
font-size: 3rem;
}
.weather-details {
grid-template-columns: 1fr;
gap: 10px;
}
.detail-item {
padding: 12px;
}
.index-grid {
grid-template-columns: 1fr;
gap: 15px;
}
.index-item {
padding: 15px;
}
.index-icon {
font-size: 1.5rem;
margin-right: 10px;
}
.life-index h3 {
font-size: 1.3rem;
margin-bottom: 15px;
}
.location-info h2 {
font-size: 1.5rem;
}
}
/* 超小屏幕适配 */
@media (max-width: 480px) {
.container {
padding: 10px;
}
.header h1 {
font-size: 1.8rem;
}
.weather-card {
padding: 15px;
}
.temperature {
font-size: 2.5rem;
}
.current-weather {
padding: 15px;
}
.detail-item {
padding: 10px;
font-size: 14px;
}
.index-item {
padding: 12px;
}
.index-desc {
font-size: 13px;
}
}

View File

@@ -1,68 +1,68 @@
{
"code": 200,
"message": "获取成功。数据来自官方/权威源头,以确保稳定与实时。开源地址 https://github.com/vikiboss/60s反馈群 595941841",
"data": {
"location": {
"province": "北京",
"city": "北京",
"town": "北京",
"formatted": "北京",
"location_id": "101010100",
"detail_url": "http://www.weather.com.cn/weather/101010100.shtml",
"is_province": true,
"is_city": false,
"is_town": false,
"area_code": "10",
"zip_code": "100000"
},
"realtime": {
"weather": "晴转雷阵雨",
"weather_desc": "未知",
"weather_code": "d0",
"temperature": 999,
"temperature_feels_like": 75.6,
"humidity": 63,
"wind_direction": "南风",
"wind_strength": "3-4级转\u003C3级",
"wind_speed": "1km/h",
"pressure": 1006,
"visibility": "21km",
"aqi": 41,
"pm25": 41,
"rainfall": 0,
"rainfall_24h": 0,
"updated": "2025-09-08 08:00:00",
"updated_at": "20:30",
"life_index": {
"comfort": {
"level": "舒适",
"desc": "白天温度宜人,风力不大。"
},
"clothing": {
"level": "舒适",
"desc": "建议穿长袖衬衫单裤等服装。"
},
"umbrella": {
"level": "带伞",
"desc": "有降水,短时间出行不必带伞。"
},
"uv": {
"level": "最弱",
"desc": "辐射弱涂擦SPF8-12防晒护肤品。"
},
"car_wash": {
"level": "不宜",
"desc": "有雨,雨水和泥水会弄脏爱车。"
},
"travel": {
"level": "一般",
"desc": "可能有雷暴,外出请尽量避开降雨时段。"
},
"sport": {
"level": "较不宜",
"desc": "有降水,推荐您在室内进行休闲运动。"
}
}
}
}
{
"code": 200,
"message": "获取成功。数据来自官方/权威源头,以确保稳定与实时。开源地址 https://github.com/vikiboss/60s反馈群 595941841",
"data": {
"location": {
"province": "北京",
"city": "北京",
"town": "北京",
"formatted": "北京",
"location_id": "101010100",
"detail_url": "http://www.weather.com.cn/weather/101010100.shtml",
"is_province": true,
"is_city": false,
"is_town": false,
"area_code": "10",
"zip_code": "100000"
},
"realtime": {
"weather": "晴转雷阵雨",
"weather_desc": "未知",
"weather_code": "d0",
"temperature": 999,
"temperature_feels_like": 75.6,
"humidity": 63,
"wind_direction": "南风",
"wind_strength": "3-4级转\u003C3级",
"wind_speed": "1km/h",
"pressure": 1006,
"visibility": "21km",
"aqi": 41,
"pm25": 41,
"rainfall": 0,
"rainfall_24h": 0,
"updated": "2025-09-08 08:00:00",
"updated_at": "20:30",
"life_index": {
"comfort": {
"level": "舒适",
"desc": "白天温度宜人,风力不大。"
},
"clothing": {
"level": "舒适",
"desc": "建议穿长袖衬衫单裤等服装。"
},
"umbrella": {
"level": "带伞",
"desc": "有降水,短时间出行不必带伞。"
},
"uv": {
"level": "最弱",
"desc": "辐射弱涂擦SPF8-12防晒护肤品。"
},
"car_wash": {
"level": "不宜",
"desc": "有雨,雨水和泥水会弄脏爱车。"
},
"travel": {
"level": "一般",
"desc": "可能有雷暴,外出请尽量避开降雨时段。"
},
"sport": {
"level": "较不宜",
"desc": "有降水,推荐您在室内进行休闲运动。"
}
}
}
}
}

View File

@@ -1,218 +1,218 @@
<!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="preconnect" href="https://60s.api.shumengya.top">
<link rel="dns-prefetch" href="https://60s.api.shumengya.top">
<link rel="stylesheet" href="css/style.css">
<link rel="stylesheet" href="css/background.css">
</head>
<body>
<div class="container">
<header class="header">
<h1>🔒 密码强度检测器</h1>
<p class="subtitle">实时分析密码安全性,保护您的数字生活</p>
</header>
<main class="main-content">
<!-- 密码输入区域 -->
<div class="input-container">
<div class="input-group">
<label for="passwordInput" class="input-label">请输入要检测的密码</label>
<div class="password-input-wrapper">
<input
type="password"
id="passwordInput"
class="password-input"
placeholder="输入您的密码进行安全性检测..."
autocomplete="new-password"
spellcheck="false"
>
<button type="button" class="toggle-visibility" id="toggleVisibility" title="显示/隐藏密码">
<svg class="eye-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path>
<circle cx="12" cy="12" r="3"></circle>
</svg>
<svg class="eye-off-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="display: none;">
<path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"></path>
<line x1="1" y1="1" x2="23" y2="23"></line>
</svg>
</button>
</div>
<div class="input-hint">
<span class="hint-icon">💡</span>
<span class="hint-text">输入密码后将实时显示安全性分析结果</span>
</div>
</div>
<button type="button" class="check-btn" id="checkBtn">
<span class="btn-icon">🔍</span>
<span class="btn-text">检测密码强度</span>
<span class="btn-loading" style="display: none;">检测中...</span>
</button>
</div>
<!-- 结果显示区域 -->
<div class="result-container" id="resultContainer" style="display: none;">
<!-- 密码强度概览 -->
<div class="strength-overview">
<div class="strength-score">
<div class="score-circle" id="scoreCircle">
<div class="score-value" id="scoreValue">0</div>
<div class="score-label"></div>
</div>
<div class="strength-info">
<div class="strength-level" id="strengthLevel">未知</div>
<div class="strength-description" id="strengthDescription">请输入密码进行检测</div>
</div>
</div>
<div class="strength-bar">
<div class="bar-background">
<div class="bar-fill" id="strengthBar"></div>
</div>
<div class="bar-labels">
<span></span>
<span>中等</span>
<span></span>
<span>非常强</span>
</div>
</div>
</div>
<!-- 详细信息 -->
<div class="details-grid">
<div class="detail-card">
<div class="card-header">
<span class="card-icon">📏</span>
<h3>基本信息</h3>
</div>
<div class="card-content">
<div class="info-row">
<span class="info-label">密码长度:</span>
<span class="info-value" id="passwordLength">-</span>
</div>
<div class="info-row">
<span class="info-label">熵值:</span>
<span class="info-value" id="entropyValue">-</span>
</div>
<div class="info-row">
<span class="info-label">破解时间:</span>
<span class="info-value" id="crackTime">-</span>
</div>
<div class="info-row">
<span class="info-label">字符种类:</span>
<span class="info-value" id="characterVariety">-</span>
</div>
</div>
</div>
<div class="detail-card">
<div class="card-header">
<span class="card-icon">🔤</span>
<h3>字符分析</h3>
</div>
<div class="card-content">
<div class="character-types" id="characterTypes">
<div class="char-type" id="hasLowercase">
<span class="type-icon"></span>
<span class="type-text">小写字母</span>
</div>
<div class="char-type" id="hasUppercase">
<span class="type-icon"></span>
<span class="type-text">大写字母</span>
</div>
<div class="char-type" id="hasNumbers">
<span class="type-icon"></span>
<span class="type-text">数字</span>
</div>
<div class="char-type" id="hasSymbols">
<span class="type-icon"></span>
<span class="type-text">特殊符号</span>
</div>
</div>
<div class="character-issues" id="characterIssues">
<div class="issue-item" id="hasRepeated">
<span class="issue-icon">⚠️</span>
<span class="issue-text">包含重复字符</span>
</div>
<div class="issue-item" id="hasSequential">
<span class="issue-icon">⚠️</span>
<span class="issue-text">包含连续字符</span>
</div>
</div>
</div>
</div>
</div>
<!-- 建议和提示 -->
<div class="recommendations-section">
<div class="recommendations-card">
<div class="card-header">
<span class="card-icon">💡</span>
<h3>改进建议</h3>
</div>
<div class="card-content">
<ul class="recommendations-list" id="recommendationsList">
<li>请输入密码进行分析</li>
</ul>
</div>
</div>
<div class="security-tips-card">
<div class="card-header">
<span class="card-icon">🛡️</span>
<h3>安全提示</h3>
</div>
<div class="card-content">
<div class="tips-container" id="securityTips">
<div class="tip-item">
<span class="tip-icon">🔐</span>
<span class="tip-text">使用密码管理器生成和存储复杂密码</span>
</div>
<div class="tip-item">
<span class="tip-icon">🔄</span>
<span class="tip-text">为不同账户使用不同的密码</span>
</div>
<div class="tip-item">
<span class="tip-icon"></span>
<span class="tip-text">定期更换重要账户的密码</span>
</div>
<div class="tip-item">
<span class="tip-icon">🔒</span>
<span class="tip-text">启用双因素认证2FA增强安全性</span>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 错误显示区域 -->
<div class="error-container" id="errorContainer" style="display: none;">
<div class="error-icon">⚠️</div>
<h3>检测失败</h3>
<p id="errorMessage">请检查网络连接后重试</p>
<button class="retry-btn" id="retryBtn">重新检测</button>
</div>
</main>
<footer class="footer">
<p>🔒 保护您的数字安全,从强密码开始</p>
<p class="footer-note">本工具不会存储您的密码信息</p>
</footer>
</div>
<!-- 提示框 -->
<div class="toast" id="toast" style="display: none;">
<span id="toastMessage">操作成功</span>
</div>
<script src="js/script.js"></script>
</body>
<!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="preconnect" href="https://60s.api.shumengya.top">
<link rel="dns-prefetch" href="https://60s.api.shumengya.top">
<link rel="stylesheet" href="css/style.css">
<link rel="stylesheet" href="css/background.css">
</head>
<body>
<div class="container">
<header class="header">
<h1>🔒 密码强度检测器</h1>
<p class="subtitle">实时分析密码安全性,保护您的数字生活</p>
</header>
<main class="main-content">
<!-- 密码输入区域 -->
<div class="input-container">
<div class="input-group">
<label for="passwordInput" class="input-label">请输入要检测的密码</label>
<div class="password-input-wrapper">
<input
type="password"
id="passwordInput"
class="password-input"
placeholder="输入您的密码进行安全性检测..."
autocomplete="new-password"
spellcheck="false"
>
<button type="button" class="toggle-visibility" id="toggleVisibility" title="显示/隐藏密码">
<svg class="eye-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path>
<circle cx="12" cy="12" r="3"></circle>
</svg>
<svg class="eye-off-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="display: none;">
<path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"></path>
<line x1="1" y1="1" x2="23" y2="23"></line>
</svg>
</button>
</div>
<div class="input-hint">
<span class="hint-icon">💡</span>
<span class="hint-text">输入密码后将实时显示安全性分析结果</span>
</div>
</div>
<button type="button" class="check-btn" id="checkBtn">
<span class="btn-icon">🔍</span>
<span class="btn-text">检测密码强度</span>
<span class="btn-loading" style="display: none;">检测中...</span>
</button>
</div>
<!-- 结果显示区域 -->
<div class="result-container" id="resultContainer" style="display: none;">
<!-- 密码强度概览 -->
<div class="strength-overview">
<div class="strength-score">
<div class="score-circle" id="scoreCircle">
<div class="score-value" id="scoreValue">0</div>
<div class="score-label"></div>
</div>
<div class="strength-info">
<div class="strength-level" id="strengthLevel">未知</div>
<div class="strength-description" id="strengthDescription">请输入密码进行检测</div>
</div>
</div>
<div class="strength-bar">
<div class="bar-background">
<div class="bar-fill" id="strengthBar"></div>
</div>
<div class="bar-labels">
<span></span>
<span>中等</span>
<span></span>
<span>非常强</span>
</div>
</div>
</div>
<!-- 详细信息 -->
<div class="details-grid">
<div class="detail-card">
<div class="card-header">
<span class="card-icon">📏</span>
<h3>基本信息</h3>
</div>
<div class="card-content">
<div class="info-row">
<span class="info-label">密码长度:</span>
<span class="info-value" id="passwordLength">-</span>
</div>
<div class="info-row">
<span class="info-label">熵值:</span>
<span class="info-value" id="entropyValue">-</span>
</div>
<div class="info-row">
<span class="info-label">破解时间:</span>
<span class="info-value" id="crackTime">-</span>
</div>
<div class="info-row">
<span class="info-label">字符种类:</span>
<span class="info-value" id="characterVariety">-</span>
</div>
</div>
</div>
<div class="detail-card">
<div class="card-header">
<span class="card-icon">🔤</span>
<h3>字符分析</h3>
</div>
<div class="card-content">
<div class="character-types" id="characterTypes">
<div class="char-type" id="hasLowercase">
<span class="type-icon"></span>
<span class="type-text">小写字母</span>
</div>
<div class="char-type" id="hasUppercase">
<span class="type-icon"></span>
<span class="type-text">大写字母</span>
</div>
<div class="char-type" id="hasNumbers">
<span class="type-icon"></span>
<span class="type-text">数字</span>
</div>
<div class="char-type" id="hasSymbols">
<span class="type-icon"></span>
<span class="type-text">特殊符号</span>
</div>
</div>
<div class="character-issues" id="characterIssues">
<div class="issue-item" id="hasRepeated">
<span class="issue-icon">⚠️</span>
<span class="issue-text">包含重复字符</span>
</div>
<div class="issue-item" id="hasSequential">
<span class="issue-icon">⚠️</span>
<span class="issue-text">包含连续字符</span>
</div>
</div>
</div>
</div>
</div>
<!-- 建议和提示 -->
<div class="recommendations-section">
<div class="recommendations-card">
<div class="card-header">
<span class="card-icon">💡</span>
<h3>改进建议</h3>
</div>
<div class="card-content">
<ul class="recommendations-list" id="recommendationsList">
<li>请输入密码进行分析</li>
</ul>
</div>
</div>
<div class="security-tips-card">
<div class="card-header">
<span class="card-icon">🛡️</span>
<h3>安全提示</h3>
</div>
<div class="card-content">
<div class="tips-container" id="securityTips">
<div class="tip-item">
<span class="tip-icon">🔐</span>
<span class="tip-text">使用密码管理器生成和存储复杂密码</span>
</div>
<div class="tip-item">
<span class="tip-icon">🔄</span>
<span class="tip-text">为不同账户使用不同的密码</span>
</div>
<div class="tip-item">
<span class="tip-icon"></span>
<span class="tip-text">定期更换重要账户的密码</span>
</div>
<div class="tip-item">
<span class="tip-icon">🔒</span>
<span class="tip-text">启用双因素认证2FA增强安全性</span>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 错误显示区域 -->
<div class="error-container" id="errorContainer" style="display: none;">
<div class="error-icon">⚠️</div>
<h3>检测失败</h3>
<p id="errorMessage">请检查网络连接后重试</p>
<button class="retry-btn" id="retryBtn">重新检测</button>
</div>
</main>
<footer class="footer">
<p>🔒 保护您的数字安全,从强密码开始</p>
<p class="footer-note">本工具不会存储您的密码信息</p>
</footer>
</div>
<!-- 提示框 -->
<div class="toast" id="toast" style="display: none;">
<span id="toastMessage">操作成功</span>
</div>
<script src="js/script.js"></script>
</body>
</html>

View File

@@ -1,37 +1,37 @@
{
"code": 200,
"message": "获取成功。数据来自官方/权威源头,以确保稳定与实时。开源地址 https://github.com/vikiboss/60s反馈群 595941841",
"data": {
"password": "adasdasdasdadasd",
"length": 16,
"score": 68,
"strength": "中等",
"entropy": 75.21,
"time_to_crack": "数百万年",
"character_analysis": {
"has_lowercase": true,
"has_uppercase": false,
"has_numbers": false,
"has_symbols": false,
"has_repeated": false,
"has_sequential": true,
"character_variety": 26
},
"recommendations": [
"建议包含大写字母",
"建议包含数字",
"建议包含特殊符号",
"避免使用连续序列字符"
],
"security_tips": [
"使用密码管理器生成和存储复杂密码",
"为不同账户使用不同的密码",
"定期更换重要账户的密码",
"启用双因素认证2FA增强安全性",
"避免在公共场合输入密码",
"不要将密码保存在浏览器中(除非使用可信的密码管理器)",
"避免使用个人信息作为密码",
"长密码比复杂密码更安全"
]
}
{
"code": 200,
"message": "获取成功。数据来自官方/权威源头,以确保稳定与实时。开源地址 https://github.com/vikiboss/60s反馈群 595941841",
"data": {
"password": "adasdasdasdadasd",
"length": 16,
"score": 68,
"strength": "中等",
"entropy": 75.21,
"time_to_crack": "数百万年",
"character_analysis": {
"has_lowercase": true,
"has_uppercase": false,
"has_numbers": false,
"has_symbols": false,
"has_repeated": false,
"has_sequential": true,
"character_variety": 26
},
"recommendations": [
"建议包含大写字母",
"建议包含数字",
"建议包含特殊符号",
"避免使用连续序列字符"
],
"security_tips": [
"使用密码管理器生成和存储复杂密码",
"为不同账户使用不同的密码",
"定期更换重要账户的密码",
"启用双因素认证2FA增强安全性",
"避免在公共场合输入密码",
"不要将密码保存在浏览器中(除非使用可信的密码管理器)",
"避免使用个人信息作为密码",
"长密码比复杂密码更安全"
]
}
}

View File

@@ -1,132 +1,132 @@
/* 背景样式文件 - 独立分离便于迁移 */
/* 主背景渐变 */
body {
background: linear-gradient(135deg,
#e8f5e8 0%,
#f1f8e9 25%,
#e8f5e8 50%,
#c8e6c9 75%,
#e8f5e8 100%);
background-size: 400% 400%;
animation: backgroundShift 15s ease-in-out infinite;
position: relative;
}
/* 背景动画 */
@keyframes backgroundShift {
0%, 100% {
background-position: 0% 50%;
}
25% {
background-position: 100% 50%;
}
50% {
background-position: 50% 100%;
}
75% {
background-position: 50% 0%;
}
}
/* 背景装饰元素 */
body::before {
content: '';
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image:
radial-gradient(circle at 20% 20%, rgba(76, 175, 80, 0.1) 0%, transparent 50%),
radial-gradient(circle at 80% 80%, rgba(129, 199, 132, 0.1) 0%, transparent 50%),
radial-gradient(circle at 40% 60%, rgba(165, 214, 167, 0.08) 0%, transparent 50%);
pointer-events: none;
z-index: -1;
animation: floatingBubbles 20s ease-in-out infinite;
}
@keyframes floatingBubbles {
0%, 100% {
transform: translateY(0px) rotate(0deg);
opacity: 1;
}
33% {
transform: translateY(-20px) rotate(120deg);
opacity: 0.8;
}
66% {
transform: translateY(10px) rotate(240deg);
opacity: 0.9;
}
}
/* 背景粒子效果 */
body::after {
content: '';
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image:
radial-gradient(2px 2px at 20px 30px, rgba(76, 175, 80, 0.3), transparent),
radial-gradient(2px 2px at 40px 70px, rgba(129, 199, 132, 0.2), transparent),
radial-gradient(1px 1px at 90px 40px, rgba(165, 214, 167, 0.3), transparent),
radial-gradient(1px 1px at 130px 80px, rgba(76, 175, 80, 0.2), transparent),
radial-gradient(2px 2px at 160px 30px, rgba(129, 199, 132, 0.3), transparent);
background-repeat: repeat;
background-size: 200px 100px;
pointer-events: none;
z-index: -1;
animation: particleFloat 25s linear infinite;
}
@keyframes particleFloat {
0% {
transform: translateY(0px);
}
100% {
transform: translateY(-100px);
}
}
/* 响应式背景调整 */
@media (max-width: 768px) {
body::after {
background-size: 150px 75px;
animation-duration: 20s;
}
body::before {
animation-duration: 15s;
}
}
@media (max-width: 480px) {
body::after {
background-size: 100px 50px;
animation-duration: 15s;
}
body::before {
animation-duration: 12s;
}
body {
animation-duration: 12s;
}
}
/* 高性能模式 - 减少动画 */
@media (prefers-reduced-motion: reduce) {
body,
body::before,
body::after {
animation: none;
}
body {
background: linear-gradient(135deg, #e8f5e8 0%, #f1f8e9 50%, #c8e6c9 100%);
}
/* 背景样式文件 - 独立分离便于迁移 */
/* 主背景渐变 */
body {
background: linear-gradient(135deg,
#e8f5e8 0%,
#f1f8e9 25%,
#e8f5e8 50%,
#c8e6c9 75%,
#e8f5e8 100%);
background-size: 400% 400%;
animation: backgroundShift 15s ease-in-out infinite;
position: relative;
}
/* 背景动画 */
@keyframes backgroundShift {
0%, 100% {
background-position: 0% 50%;
}
25% {
background-position: 100% 50%;
}
50% {
background-position: 50% 100%;
}
75% {
background-position: 50% 0%;
}
}
/* 背景装饰元素 */
body::before {
content: '';
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image:
radial-gradient(circle at 20% 20%, rgba(76, 175, 80, 0.1) 0%, transparent 50%),
radial-gradient(circle at 80% 80%, rgba(129, 199, 132, 0.1) 0%, transparent 50%),
radial-gradient(circle at 40% 60%, rgba(165, 214, 167, 0.08) 0%, transparent 50%);
pointer-events: none;
z-index: -1;
animation: floatingBubbles 20s ease-in-out infinite;
}
@keyframes floatingBubbles {
0%, 100% {
transform: translateY(0px) rotate(0deg);
opacity: 1;
}
33% {
transform: translateY(-20px) rotate(120deg);
opacity: 0.8;
}
66% {
transform: translateY(10px) rotate(240deg);
opacity: 0.9;
}
}
/* 背景粒子效果 */
body::after {
content: '';
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image:
radial-gradient(2px 2px at 20px 30px, rgba(76, 175, 80, 0.3), transparent),
radial-gradient(2px 2px at 40px 70px, rgba(129, 199, 132, 0.2), transparent),
radial-gradient(1px 1px at 90px 40px, rgba(165, 214, 167, 0.3), transparent),
radial-gradient(1px 1px at 130px 80px, rgba(76, 175, 80, 0.2), transparent),
radial-gradient(2px 2px at 160px 30px, rgba(129, 199, 132, 0.3), transparent);
background-repeat: repeat;
background-size: 200px 100px;
pointer-events: none;
z-index: -1;
animation: particleFloat 25s linear infinite;
}
@keyframes particleFloat {
0% {
transform: translateY(0px);
}
100% {
transform: translateY(-100px);
}
}
/* 响应式背景调整 */
@media (max-width: 768px) {
body::after {
background-size: 150px 75px;
animation-duration: 20s;
}
body::before {
animation-duration: 15s;
}
}
@media (max-width: 480px) {
body::after {
background-size: 100px 50px;
animation-duration: 15s;
}
body::before {
animation-duration: 12s;
}
body {
animation-duration: 12s;
}
}
/* 高性能模式 - 减少动画 */
@media (prefers-reduced-motion: reduce) {
body,
body::before,
body::after {
animation: none;
}
body {
background: linear-gradient(135deg, #e8f5e8 0%, #f1f8e9 50%, #c8e6c9 100%);
}
}

View File

@@ -1,468 +1,468 @@
/* 基础样式重置 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
line-height: 1.6;
color: #2d5a3d;
min-height: 100vh;
overflow-x: hidden;
}
/* 容器样式 */
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
min-height: 100vh;
display: flex;
flex-direction: column;
position: relative;
z-index: 1;
}
/* 头部样式 */
.header {
text-align: center;
margin-bottom: 40px;
padding: 40px 20px;
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20px);
border-radius: 20px;
box-shadow: 0 8px 32px rgba(76, 175, 80, 0.1);
border: 1px solid rgba(76, 175, 80, 0.2);
position: relative;
overflow: hidden;
}
.header::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 4px;
background: linear-gradient(90deg, transparent, #4caf50, transparent);
animation: headerGlow 3s ease-in-out infinite;
}
@keyframes headerGlow {
0% { left: -100%; }
50% { left: 100%; }
100% { left: 100%; }
}
.header h1 {
font-size: 2.5rem;
font-weight: 700;
background: linear-gradient(135deg, #4caf50, #81c784, #4caf50);
background-size: 200% 200%;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
margin-bottom: 10px;
animation: titleGradient 4s ease-in-out infinite;
}
@keyframes titleGradient {
0%, 100% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
}
.header p {
font-size: 1.1rem;
color: #66bb6a;
opacity: 0.9;
}
/* 主要内容区域 */
.main {
flex: 1;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 40px;
align-items: start;
}
/* 表单容器 */
.form-container {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20px);
padding: 35px;
border-radius: 20px;
box-shadow: 0 8px 32px rgba(76, 175, 80, 0.1);
border: 1px solid rgba(76, 175, 80, 0.2);
transition: all 0.3s ease;
}
.form-container:hover {
transform: translateY(-5px);
box-shadow: 0 12px 40px rgba(76, 175, 80, 0.15);
}
/* 表单样式 */
.qr-form {
display: flex;
flex-direction: column;
gap: 25px;
}
.input-group {
display: flex;
flex-direction: column;
gap: 8px;
}
.input-group label {
font-weight: 600;
color: #2d5a3d;
font-size: 0.95rem;
}
textarea, select {
padding: 12px 16px;
border: 2px solid rgba(76, 175, 80, 0.3);
border-radius: 12px;
font-size: 1rem;
background: rgba(255, 255, 255, 0.9);
transition: all 0.3s ease;
resize: vertical;
}
textarea {
min-height: 100px;
font-family: inherit;
}
textarea:focus, select:focus {
outline: none;
border-color: #4caf50;
box-shadow: 0 0 0 3px rgba(76, 175, 80, 0.1);
background: rgba(255, 255, 255, 1);
}
.options-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 20px;
}
/* 按钮样式 */
.generate-btn {
padding: 15px 30px;
background: linear-gradient(135deg, #4caf50, #66bb6a);
color: white;
border: none;
border-radius: 12px;
font-size: 1.1rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
letter-spacing: 0.5px;
}
.generate-btn::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
transition: left 0.5s ease;
}
.generate-btn:hover::before {
left: 100%;
}
.generate-btn:hover {
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(76, 175, 80, 0.3);
}
.generate-btn:active {
transform: translateY(0);
}
.generate-btn.loading .btn-text {
display: none;
}
.generate-btn.loading .btn-loading {
display: inline;
}
.btn-loading {
display: none;
}
/* 结果容器 */
.result-container {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20px);
padding: 35px;
border-radius: 20px;
box-shadow: 0 8px 32px rgba(76, 175, 80, 0.1);
border: 1px solid rgba(76, 175, 80, 0.2);
min-height: 400px;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
}
/* 加载动画 */
.loading {
text-align: center;
color: #4caf50;
}
.loading-spinner {
width: 50px;
height: 50px;
border: 4px solid rgba(76, 175, 80, 0.2);
border-top: 4px solid #4caf50;
border-radius: 50%;
animation: spin 1s linear infinite;
margin: 0 auto 20px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* 错误样式 */
.error {
text-align: center;
color: #d32f2f;
}
.error-icon {
font-size: 3rem;
margin-bottom: 15px;
}
.error-message {
font-size: 1.1rem;
margin-bottom: 20px;
color: #666;
}
.retry-btn {
padding: 10px 20px;
background: #4caf50;
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s ease;
}
.retry-btn:hover {
background: #45a049;
transform: translateY(-1px);
}
/* 结果显示 */
.result {
width: 100%;
text-align: center;
}
.qr-display {
margin-bottom: 25px;
}
.qr-display img {
max-width: 100%;
height: auto;
border-radius: 12px;
box-shadow: 0 4px 20px rgba(76, 175, 80, 0.2);
transition: all 0.3s ease;
}
.qr-display img:hover {
transform: scale(1.05);
box-shadow: 0 8px 30px rgba(76, 175, 80, 0.3);
}
.result-info {
display: flex;
flex-direction: column;
gap: 20px;
}
.result-text {
font-size: 1rem;
color: #666;
word-break: break-all;
background: rgba(76, 175, 80, 0.1);
padding: 12px;
border-radius: 8px;
border-left: 4px solid #4caf50;
}
.result-actions {
display: flex;
gap: 10px;
justify-content: center;
flex-wrap: wrap;
}
.download-btn, .copy-btn, .new-btn {
padding: 10px 16px;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 0.9rem;
font-weight: 500;
transition: all 0.3s ease;
}
.download-btn {
background: #4caf50;
color: white;
}
.copy-btn {
background: #2196f3;
color: white;
}
.new-btn {
background: #ff9800;
color: white;
}
.download-btn:hover, .copy-btn:hover, .new-btn:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
}
/* 隐藏类 */
.hidden {
display: none !important;
}
/* 页脚样式 */
.footer {
text-align: center;
margin-top: 40px;
padding: 25px;
background: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(15px);
border-radius: 15px;
color: #66bb6a;
font-size: 0.9rem;
border: 1px solid rgba(76, 175, 80, 0.1);
}
/* 平板端适配 (768px - 1024px) */
@media (max-width: 1024px) and (min-width: 768px) {
.container {
max-width: 95%;
padding: 15px;
}
.main {
gap: 30px;
}
.header h1 {
font-size: 2.2rem;
}
.form-container, .result-container {
padding: 25px;
}
.options-grid {
grid-template-columns: repeat(2, 1fr);
}
}
/* 手机端适配 (最大768px) */
@media (max-width: 768px) {
.container {
padding: 10px;
}
.header {
margin-bottom: 25px;
padding: 25px 15px;
}
.header h1 {
font-size: 1.8rem;
}
.header p {
font-size: 1rem;
}
.main {
grid-template-columns: 1fr;
gap: 25px;
}
.form-container, .result-container {
padding: 20px;
}
.options-grid {
grid-template-columns: 1fr;
gap: 15px;
}
.result-actions {
flex-direction: column;
align-items: center;
}
.download-btn, .copy-btn, .new-btn {
width: 100%;
max-width: 200px;
}
}
/* 小屏手机适配 (最大480px) */
@media (max-width: 480px) {
.container {
padding: 8px;
}
.header {
padding: 20px 10px;
margin-bottom: 20px;
}
.header h1 {
font-size: 1.6rem;
}
.form-container, .result-container {
padding: 15px;
border-radius: 15px;
}
.generate-btn {
padding: 12px 20px;
font-size: 1rem;
}
textarea {
min-height: 80px;
}
.qr-display img {
max-width: 90%;
}
/* 基础样式重置 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
line-height: 1.6;
color: #2d5a3d;
min-height: 100vh;
overflow-x: hidden;
}
/* 容器样式 */
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
min-height: 100vh;
display: flex;
flex-direction: column;
position: relative;
z-index: 1;
}
/* 头部样式 */
.header {
text-align: center;
margin-bottom: 40px;
padding: 40px 20px;
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20px);
border-radius: 20px;
box-shadow: 0 8px 32px rgba(76, 175, 80, 0.1);
border: 1px solid rgba(76, 175, 80, 0.2);
position: relative;
overflow: hidden;
}
.header::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 4px;
background: linear-gradient(90deg, transparent, #4caf50, transparent);
animation: headerGlow 3s ease-in-out infinite;
}
@keyframes headerGlow {
0% { left: -100%; }
50% { left: 100%; }
100% { left: 100%; }
}
.header h1 {
font-size: 2.5rem;
font-weight: 700;
background: linear-gradient(135deg, #4caf50, #81c784, #4caf50);
background-size: 200% 200%;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
margin-bottom: 10px;
animation: titleGradient 4s ease-in-out infinite;
}
@keyframes titleGradient {
0%, 100% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
}
.header p {
font-size: 1.1rem;
color: #66bb6a;
opacity: 0.9;
}
/* 主要内容区域 */
.main {
flex: 1;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 40px;
align-items: start;
}
/* 表单容器 */
.form-container {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20px);
padding: 35px;
border-radius: 20px;
box-shadow: 0 8px 32px rgba(76, 175, 80, 0.1);
border: 1px solid rgba(76, 175, 80, 0.2);
transition: all 0.3s ease;
}
.form-container:hover {
transform: translateY(-5px);
box-shadow: 0 12px 40px rgba(76, 175, 80, 0.15);
}
/* 表单样式 */
.qr-form {
display: flex;
flex-direction: column;
gap: 25px;
}
.input-group {
display: flex;
flex-direction: column;
gap: 8px;
}
.input-group label {
font-weight: 600;
color: #2d5a3d;
font-size: 0.95rem;
}
textarea, select {
padding: 12px 16px;
border: 2px solid rgba(76, 175, 80, 0.3);
border-radius: 12px;
font-size: 1rem;
background: rgba(255, 255, 255, 0.9);
transition: all 0.3s ease;
resize: vertical;
}
textarea {
min-height: 100px;
font-family: inherit;
}
textarea:focus, select:focus {
outline: none;
border-color: #4caf50;
box-shadow: 0 0 0 3px rgba(76, 175, 80, 0.1);
background: rgba(255, 255, 255, 1);
}
.options-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 20px;
}
/* 按钮样式 */
.generate-btn {
padding: 15px 30px;
background: linear-gradient(135deg, #4caf50, #66bb6a);
color: white;
border: none;
border-radius: 12px;
font-size: 1.1rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
letter-spacing: 0.5px;
}
.generate-btn::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
transition: left 0.5s ease;
}
.generate-btn:hover::before {
left: 100%;
}
.generate-btn:hover {
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(76, 175, 80, 0.3);
}
.generate-btn:active {
transform: translateY(0);
}
.generate-btn.loading .btn-text {
display: none;
}
.generate-btn.loading .btn-loading {
display: inline;
}
.btn-loading {
display: none;
}
/* 结果容器 */
.result-container {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20px);
padding: 35px;
border-radius: 20px;
box-shadow: 0 8px 32px rgba(76, 175, 80, 0.1);
border: 1px solid rgba(76, 175, 80, 0.2);
min-height: 400px;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
}
/* 加载动画 */
.loading {
text-align: center;
color: #4caf50;
}
.loading-spinner {
width: 50px;
height: 50px;
border: 4px solid rgba(76, 175, 80, 0.2);
border-top: 4px solid #4caf50;
border-radius: 50%;
animation: spin 1s linear infinite;
margin: 0 auto 20px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* 错误样式 */
.error {
text-align: center;
color: #d32f2f;
}
.error-icon {
font-size: 3rem;
margin-bottom: 15px;
}
.error-message {
font-size: 1.1rem;
margin-bottom: 20px;
color: #666;
}
.retry-btn {
padding: 10px 20px;
background: #4caf50;
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s ease;
}
.retry-btn:hover {
background: #45a049;
transform: translateY(-1px);
}
/* 结果显示 */
.result {
width: 100%;
text-align: center;
}
.qr-display {
margin-bottom: 25px;
}
.qr-display img {
max-width: 100%;
height: auto;
border-radius: 12px;
box-shadow: 0 4px 20px rgba(76, 175, 80, 0.2);
transition: all 0.3s ease;
}
.qr-display img:hover {
transform: scale(1.05);
box-shadow: 0 8px 30px rgba(76, 175, 80, 0.3);
}
.result-info {
display: flex;
flex-direction: column;
gap: 20px;
}
.result-text {
font-size: 1rem;
color: #666;
word-break: break-all;
background: rgba(76, 175, 80, 0.1);
padding: 12px;
border-radius: 8px;
border-left: 4px solid #4caf50;
}
.result-actions {
display: flex;
gap: 10px;
justify-content: center;
flex-wrap: wrap;
}
.download-btn, .copy-btn, .new-btn {
padding: 10px 16px;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 0.9rem;
font-weight: 500;
transition: all 0.3s ease;
}
.download-btn {
background: #4caf50;
color: white;
}
.copy-btn {
background: #2196f3;
color: white;
}
.new-btn {
background: #ff9800;
color: white;
}
.download-btn:hover, .copy-btn:hover, .new-btn:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
}
/* 隐藏类 */
.hidden {
display: none !important;
}
/* 页脚样式 */
.footer {
text-align: center;
margin-top: 40px;
padding: 25px;
background: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(15px);
border-radius: 15px;
color: #66bb6a;
font-size: 0.9rem;
border: 1px solid rgba(76, 175, 80, 0.1);
}
/* 平板端适配 (768px - 1024px) */
@media (max-width: 1024px) and (min-width: 768px) {
.container {
max-width: 95%;
padding: 15px;
}
.main {
gap: 30px;
}
.header h1 {
font-size: 2.2rem;
}
.form-container, .result-container {
padding: 25px;
}
.options-grid {
grid-template-columns: repeat(2, 1fr);
}
}
/* 手机端适配 (最大768px) */
@media (max-width: 768px) {
.container {
padding: 10px;
}
.header {
margin-bottom: 25px;
padding: 25px 15px;
}
.header h1 {
font-size: 1.8rem;
}
.header p {
font-size: 1rem;
}
.main {
grid-template-columns: 1fr;
gap: 25px;
}
.form-container, .result-container {
padding: 20px;
}
.options-grid {
grid-template-columns: 1fr;
gap: 15px;
}
.result-actions {
flex-direction: column;
align-items: center;
}
.download-btn, .copy-btn, .new-btn {
width: 100%;
max-width: 200px;
}
}
/* 小屏手机适配 (最大480px) */
@media (max-width: 480px) {
.container {
padding: 8px;
}
.header {
padding: 20px 10px;
margin-bottom: 20px;
}
.header h1 {
font-size: 1.6rem;
}
.form-container, .result-container {
padding: 15px;
border-radius: 15px;
}
.generate-btn {
padding: 12px 20px;
font-size: 1rem;
}
textarea {
min-height: 80px;
}
.qr-display img {
max-width: 90%;
}
}

View File

@@ -1,98 +1,98 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>二维码生成器</title>
<link rel="stylesheet" href="css/style.css">
<link rel="stylesheet" href="css/background.css">
</head>
<body>
<div class="container">
<header class="header">
<h1>🔗 二维码生成器</h1>
<p>快速生成高质量二维码</p>
</header>
<main class="main">
<div class="form-container">
<form id="qrForm" class="qr-form">
<div class="input-group">
<label for="text">文本内容</label>
<textarea id="text" name="text" placeholder="请输入要生成二维码的文本或URL" required></textarea>
</div>
<div class="options-grid">
<div class="input-group">
<label for="size">尺寸大小</label>
<select id="size" name="size">
<option value="128">128x128</option>
<option value="256" selected>256x256</option>
<option value="512">512x512</option>
<option value="1024">1024x1024</option>
</select>
</div>
<div class="input-group">
<label for="level">容错级别</label>
<select id="level" name="level">
<option value="L">L - 低 (7%)</option>
<option value="M" selected>M - 中 (15%)</option>
<option value="Q">Q - 高 (25%)</option>
<option value="H">H - 最高 (30%)</option>
</select>
</div>
<div class="input-group">
<label for="encoding">返回格式</label>
<select id="encoding" name="encoding">
<option value="image" selected>图片</option>
<option value="json">JSON</option>
<option value="text">文本</option>
</select>
</div>
</div>
<button type="submit" class="generate-btn">
<span class="btn-text">生成二维码</span>
<span class="btn-loading">生成中...</span>
</button>
</form>
</div>
<div class="result-container">
<div id="loading" class="loading hidden">
<div class="loading-spinner"></div>
<p>正在生成二维码...</p>
</div>
<div id="error" class="error hidden">
<div class="error-icon">⚠️</div>
<p class="error-message"></p>
<button class="retry-btn">重试</button>
</div>
<div id="result" class="result hidden">
<div class="qr-display">
<img id="qrImage" src="" alt="生成的二维码">
</div>
<div class="result-info">
<p class="result-text"></p>
<div class="result-actions">
<button class="download-btn">下载二维码</button>
<button class="copy-btn">复制链接</button>
<button class="new-btn">生成新的</button>
</div>
</div>
</div>
</div>
</main>
<footer class="footer">
<p>© 2024 二维码生成器 - 简单快捷的二维码生成工具</p>
</footer>
</div>
<script src="js/script.js"></script>
</body>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>二维码生成器</title>
<link rel="stylesheet" href="css/style.css">
<link rel="stylesheet" href="css/background.css">
</head>
<body>
<div class="container">
<header class="header">
<h1>🔗 二维码生成器</h1>
<p>快速生成高质量二维码</p>
</header>
<main class="main">
<div class="form-container">
<form id="qrForm" class="qr-form">
<div class="input-group">
<label for="text">文本内容</label>
<textarea id="text" name="text" placeholder="请输入要生成二维码的文本或URL" required></textarea>
</div>
<div class="options-grid">
<div class="input-group">
<label for="size">尺寸大小</label>
<select id="size" name="size">
<option value="128">128x128</option>
<option value="256" selected>256x256</option>
<option value="512">512x512</option>
<option value="1024">1024x1024</option>
</select>
</div>
<div class="input-group">
<label for="level">容错级别</label>
<select id="level" name="level">
<option value="L">L - 低 (7%)</option>
<option value="M" selected>M - 中 (15%)</option>
<option value="Q">Q - 高 (25%)</option>
<option value="H">H - 最高 (30%)</option>
</select>
</div>
<div class="input-group">
<label for="encoding">返回格式</label>
<select id="encoding" name="encoding">
<option value="image" selected>图片</option>
<option value="json">JSON</option>
<option value="text">文本</option>
</select>
</div>
</div>
<button type="submit" class="generate-btn">
<span class="btn-text">生成二维码</span>
<span class="btn-loading">生成中...</span>
</button>
</form>
</div>
<div class="result-container">
<div id="loading" class="loading hidden">
<div class="loading-spinner"></div>
<p>正在生成二维码...</p>
</div>
<div id="error" class="error hidden">
<div class="error-icon">⚠️</div>
<p class="error-message"></p>
<button class="retry-btn">重试</button>
</div>
<div id="result" class="result hidden">
<div class="qr-display">
<img id="qrImage" src="" alt="生成的二维码">
</div>
<div class="result-info">
<p class="result-text"></p>
<div class="result-actions">
<button class="download-btn">下载二维码</button>
<button class="copy-btn">复制链接</button>
<button class="new-btn">生成新的</button>
</div>
</div>
</div>
</div>
</main>
<footer class="footer">
<p>© 2024 二维码生成器 - 简单快捷的二维码生成工具</p>
</footer>
</div>
<script src="js/script.js"></script>
</body>
</html>

View File

@@ -1,453 +1,453 @@
// 二维码生成器 JavaScript
class QRCodeGenerator {
constructor() {
this.apiEndpoints = [];
this.currentApiIndex = 0;
this.init();
}
// 初始化
async init() {
await this.loadApiEndpoints();
this.bindEvents();
this.setupFormValidation();
}
// 加载API接口列表
async loadApiEndpoints() {
try {
// 直接在代码中配置API接口避免CORS问题
this.apiEndpoints = [
"https://60s.api.shumengya.top"
];
console.log('已加载API接口:', this.apiEndpoints);
} catch (error) {
console.error('加载API接口失败:', error);
this.showError('加载配置失败,请刷新页面重试');
}
}
// 绑定事件
bindEvents() {
const form = document.getElementById('qrForm');
const retryBtn = document.querySelector('.retry-btn');
const downloadBtn = document.querySelector('.download-btn');
const copyBtn = document.querySelector('.copy-btn');
const newBtn = document.querySelector('.new-btn');
const textArea = document.getElementById('text');
if (form) {
form.addEventListener('submit', (e) => this.handleSubmit(e));
}
if (retryBtn) {
retryBtn.addEventListener('click', () => this.retryGeneration());
}
if (downloadBtn) {
downloadBtn.addEventListener('click', () => this.downloadQRCode());
}
if (copyBtn) {
copyBtn.addEventListener('click', () => this.copyImageLink());
}
if (newBtn) {
newBtn.addEventListener('click', () => this.resetForm());
}
if (textArea) {
textArea.addEventListener('input', () => this.updateCharCount());
}
}
// 设置表单验证
setupFormValidation() {
const textArea = document.getElementById('text');
const form = document.getElementById('qrForm');
textArea.addEventListener('blur', () => {
if (textArea.value.trim() === '') {
this.showFieldError(textArea, '请输入要生成二维码的内容');
} else {
this.clearFieldError(textArea);
}
});
}
// 显示字段错误
showFieldError(field, message) {
this.clearFieldError(field);
field.style.borderColor = '#d32f2f';
const errorDiv = document.createElement('div');
errorDiv.className = 'field-error';
errorDiv.style.color = '#d32f2f';
errorDiv.style.fontSize = '0.8rem';
errorDiv.style.marginTop = '5px';
errorDiv.textContent = message;
field.parentNode.appendChild(errorDiv);
}
// 清除字段错误
clearFieldError(field) {
field.style.borderColor = '';
const errorDiv = field.parentNode.querySelector('.field-error');
if (errorDiv) {
errorDiv.remove();
}
}
// 更新字符计数
updateCharCount() {
const textArea = document.getElementById('text');
const text = textArea.value;
const length = text.length;
// 移除旧的计数显示
const oldCounter = textArea.parentNode.querySelector('.char-counter');
if (oldCounter) oldCounter.remove();
// 添加新的计数显示
if (length > 0) {
const counter = document.createElement('div');
counter.className = 'char-counter';
counter.style.fontSize = '0.8rem';
counter.style.color = '#666';
counter.style.textAlign = 'right';
counter.style.marginTop = '5px';
counter.textContent = `${length} 个字符`;
textArea.parentNode.appendChild(counter);
}
}
// 处理表单提交
async handleSubmit(e) {
e.preventDefault();
const formData = new FormData(e.target);
const params = {
text: formData.get('text').trim(),
size: formData.get('size'),
level: formData.get('level'),
encoding: formData.get('encoding')
};
// 验证输入
if (!params.text) {
this.showFieldError(document.getElementById('text'), '请输入要生成二维码的内容');
return;
}
this.showLoading();
await this.generateQRCode(params);
}
// 生成二维码
async generateQRCode(params) {
let success = false;
let lastError = null;
// 尝试所有API接口
for (let i = 0; i < this.apiEndpoints.length; i++) {
const apiIndex = (this.currentApiIndex + i) % this.apiEndpoints.length;
const apiUrl = this.apiEndpoints[apiIndex];
try {
console.log(`尝试API ${apiIndex + 1}:`, apiUrl);
const result = await this.callAPI(apiUrl, params);
if (result.success) {
this.currentApiIndex = apiIndex; // 记录成功的API
this.showResult(result.data, params);
success = true;
break;
}
} catch (error) {
console.warn(`API ${apiIndex + 1} 失败:`, error);
lastError = error;
}
}
if (!success) {
this.showError(lastError?.message || '所有API接口都无法访问请稍后重试');
}
}
// 调用API
async callAPI(baseUrl, params) {
const url = new URL('/v2/qrcode', baseUrl);
// 添加查询参数
Object.entries(params).forEach(([key, value]) => {
if (value !== null && value !== undefined && value !== '') {
url.searchParams.append(key, value);
}
});
console.log('请求URL:', url.toString());
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 10000); // 10秒超时
try {
const response = await fetch(url.toString(), {
method: 'GET',
signal: controller.signal,
headers: {
'Accept': 'application/json, image/*'
}
});
clearTimeout(timeoutId);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
// 根据返回格式处理
if (params.encoding === 'image' || !params.encoding) {
// 默认返回图片格式
const contentType = response.headers.get('content-type');
if (contentType && contentType.startsWith('image/')) {
const blob = await response.blob();
const imageUrl = URL.createObjectURL(blob);
return {
success: true,
data: {
imageUrl: imageUrl,
text: params.text,
size: params.size,
level: params.level,
format: 'image'
}
};
} else {
// 如果返回的不是图片尝试解析JSON
const jsonData = await response.json();
if (jsonData.code === 0 && jsonData.data && jsonData.data.data_uri) {
return {
success: true,
data: {
imageUrl: jsonData.data.data_uri,
text: params.text,
size: params.size,
level: params.level,
format: 'json',
base64: jsonData.data.base64,
mimeType: jsonData.data.mime_type
}
};
} else {
throw new Error(jsonData.message || '生成失败');
}
}
} else {
// JSON或text格式
const jsonData = await response.json();
if (jsonData.code === 0 && jsonData.data) {
return {
success: true,
data: {
imageUrl: jsonData.data.data_uri,
text: params.text,
size: params.size,
level: params.level,
format: params.encoding,
base64: jsonData.data.base64,
mimeType: jsonData.data.mime_type
}
};
} else {
throw new Error(jsonData.message || '生成失败');
}
}
} catch (error) {
clearTimeout(timeoutId);
if (error.name === 'AbortError') {
throw new Error('请求超时,请重试');
}
throw error;
}
}
// 显示加载状态
showLoading() {
this.hideAllStates();
document.getElementById('loading').classList.remove('hidden');
const btn = document.querySelector('.generate-btn');
btn.classList.add('loading');
btn.disabled = true;
}
// 显示错误
showError(message) {
this.hideAllStates();
const errorDiv = document.getElementById('error');
const errorMessage = errorDiv.querySelector('.error-message');
errorMessage.textContent = message;
errorDiv.classList.remove('hidden');
this.resetButton();
}
// 显示结果
showResult(data, params) {
this.hideAllStates();
const resultDiv = document.getElementById('result');
const qrImage = document.getElementById('qrImage');
const resultText = document.querySelector('.result-text');
qrImage.src = data.imageUrl;
qrImage.alt = `二维码: ${data.text}`;
resultText.innerHTML = `
<strong>内容:</strong> ${this.escapeHtml(data.text)}<br>
<strong>尺寸:</strong> ${data.size}x${data.size}<br>
<strong>容错级别:</strong> ${data.level}<br>
<strong>格式:</strong> ${data.format.toUpperCase()}
`;
resultDiv.classList.remove('hidden');
this.resetButton();
// 保存数据供下载使用
this.currentQRData = data;
}
// 隐藏所有状态
hideAllStates() {
document.getElementById('loading').classList.add('hidden');
document.getElementById('error').classList.add('hidden');
document.getElementById('result').classList.add('hidden');
}
// 重置按钮状态
resetButton() {
const btn = document.querySelector('.generate-btn');
btn.classList.remove('loading');
btn.disabled = false;
}
// 重试生成
async retryGeneration() {
const form = document.getElementById('qrForm');
const formData = new FormData(form);
const params = {
text: formData.get('text').trim(),
size: formData.get('size'),
level: formData.get('level'),
encoding: formData.get('encoding')
};
this.showLoading();
await this.generateQRCode(params);
}
// 下载二维码
downloadQRCode() {
if (!this.currentQRData) return;
const link = document.createElement('a');
link.href = this.currentQRData.imageUrl;
link.download = `qrcode_${Date.now()}.png`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
this.showToast('二维码已下载');
}
// 复制图片链接
async copyImageLink() {
if (!this.currentQRData) return;
try {
await navigator.clipboard.writeText(this.currentQRData.imageUrl);
this.showToast('链接已复制到剪贴板');
} catch (error) {
console.error('复制失败:', error);
this.showToast('复制失败,请手动复制');
}
}
// 重置表单
resetForm() {
document.getElementById('qrForm').reset();
this.hideAllStates();
this.currentQRData = null;
// 清除字符计数
const counter = document.querySelector('.char-counter');
if (counter) counter.remove();
// 清除字段错误
document.querySelectorAll('input, textarea, select').forEach(field => {
this.clearFieldError(field);
});
// 聚焦到文本框
document.getElementById('text').focus();
}
// 显示提示消息
showToast(message) {
// 移除旧的toast
const oldToast = document.querySelector('.toast');
if (oldToast) oldToast.remove();
const toast = document.createElement('div');
toast.className = 'toast';
toast.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: #4caf50;
color: white;
padding: 12px 20px;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
z-index: 1000;
animation: slideIn 0.3s ease;
`;
toast.textContent = message;
document.body.appendChild(toast);
setTimeout(() => {
toast.style.animation = 'slideOut 0.3s ease';
setTimeout(() => toast.remove(), 300);
}, 3000);
}
// HTML转义
escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
}
// 添加CSS动画
const style = document.createElement('style');
style.textContent = `
@keyframes slideIn {
from { transform: translateX(100%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
@keyframes slideOut {
from { transform: translateX(0); opacity: 1; }
to { transform: translateX(100%); opacity: 0; }
}
`;
document.head.appendChild(style);
// 页面加载完成后初始化
document.addEventListener('DOMContentLoaded', () => {
new QRCodeGenerator();
});
// 错误处理
window.addEventListener('error', (e) => {
console.error('全局错误:', e.error);
});
window.addEventListener('unhandledrejection', (e) => {
console.error('未处理的Promise拒绝:', e.reason);
// 二维码生成器 JavaScript
class QRCodeGenerator {
constructor() {
this.apiEndpoints = [];
this.currentApiIndex = 0;
this.init();
}
// 初始化
async init() {
await this.loadApiEndpoints();
this.bindEvents();
this.setupFormValidation();
}
// 加载API接口列表
async loadApiEndpoints() {
try {
// 直接在代码中配置API接口避免CORS问题
this.apiEndpoints = [
"https://60s.api.shumengya.top"
];
console.log('已加载API接口:', this.apiEndpoints);
} catch (error) {
console.error('加载API接口失败:', error);
this.showError('加载配置失败,请刷新页面重试');
}
}
// 绑定事件
bindEvents() {
const form = document.getElementById('qrForm');
const retryBtn = document.querySelector('.retry-btn');
const downloadBtn = document.querySelector('.download-btn');
const copyBtn = document.querySelector('.copy-btn');
const newBtn = document.querySelector('.new-btn');
const textArea = document.getElementById('text');
if (form) {
form.addEventListener('submit', (e) => this.handleSubmit(e));
}
if (retryBtn) {
retryBtn.addEventListener('click', () => this.retryGeneration());
}
if (downloadBtn) {
downloadBtn.addEventListener('click', () => this.downloadQRCode());
}
if (copyBtn) {
copyBtn.addEventListener('click', () => this.copyImageLink());
}
if (newBtn) {
newBtn.addEventListener('click', () => this.resetForm());
}
if (textArea) {
textArea.addEventListener('input', () => this.updateCharCount());
}
}
// 设置表单验证
setupFormValidation() {
const textArea = document.getElementById('text');
const form = document.getElementById('qrForm');
textArea.addEventListener('blur', () => {
if (textArea.value.trim() === '') {
this.showFieldError(textArea, '请输入要生成二维码的内容');
} else {
this.clearFieldError(textArea);
}
});
}
// 显示字段错误
showFieldError(field, message) {
this.clearFieldError(field);
field.style.borderColor = '#d32f2f';
const errorDiv = document.createElement('div');
errorDiv.className = 'field-error';
errorDiv.style.color = '#d32f2f';
errorDiv.style.fontSize = '0.8rem';
errorDiv.style.marginTop = '5px';
errorDiv.textContent = message;
field.parentNode.appendChild(errorDiv);
}
// 清除字段错误
clearFieldError(field) {
field.style.borderColor = '';
const errorDiv = field.parentNode.querySelector('.field-error');
if (errorDiv) {
errorDiv.remove();
}
}
// 更新字符计数
updateCharCount() {
const textArea = document.getElementById('text');
const text = textArea.value;
const length = text.length;
// 移除旧的计数显示
const oldCounter = textArea.parentNode.querySelector('.char-counter');
if (oldCounter) oldCounter.remove();
// 添加新的计数显示
if (length > 0) {
const counter = document.createElement('div');
counter.className = 'char-counter';
counter.style.fontSize = '0.8rem';
counter.style.color = '#666';
counter.style.textAlign = 'right';
counter.style.marginTop = '5px';
counter.textContent = `${length} 个字符`;
textArea.parentNode.appendChild(counter);
}
}
// 处理表单提交
async handleSubmit(e) {
e.preventDefault();
const formData = new FormData(e.target);
const params = {
text: formData.get('text').trim(),
size: formData.get('size'),
level: formData.get('level'),
encoding: formData.get('encoding')
};
// 验证输入
if (!params.text) {
this.showFieldError(document.getElementById('text'), '请输入要生成二维码的内容');
return;
}
this.showLoading();
await this.generateQRCode(params);
}
// 生成二维码
async generateQRCode(params) {
let success = false;
let lastError = null;
// 尝试所有API接口
for (let i = 0; i < this.apiEndpoints.length; i++) {
const apiIndex = (this.currentApiIndex + i) % this.apiEndpoints.length;
const apiUrl = this.apiEndpoints[apiIndex];
try {
console.log(`尝试API ${apiIndex + 1}:`, apiUrl);
const result = await this.callAPI(apiUrl, params);
if (result.success) {
this.currentApiIndex = apiIndex; // 记录成功的API
this.showResult(result.data, params);
success = true;
break;
}
} catch (error) {
console.warn(`API ${apiIndex + 1} 失败:`, error);
lastError = error;
}
}
if (!success) {
this.showError(lastError?.message || '所有API接口都无法访问请稍后重试');
}
}
// 调用API
async callAPI(baseUrl, params) {
const url = new URL('/v2/qrcode', baseUrl);
// 添加查询参数
Object.entries(params).forEach(([key, value]) => {
if (value !== null && value !== undefined && value !== '') {
url.searchParams.append(key, value);
}
});
console.log('请求URL:', url.toString());
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 10000); // 10秒超时
try {
const response = await fetch(url.toString(), {
method: 'GET',
signal: controller.signal,
headers: {
'Accept': 'application/json, image/*'
}
});
clearTimeout(timeoutId);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
// 根据返回格式处理
if (params.encoding === 'image' || !params.encoding) {
// 默认返回图片格式
const contentType = response.headers.get('content-type');
if (contentType && contentType.startsWith('image/')) {
const blob = await response.blob();
const imageUrl = URL.createObjectURL(blob);
return {
success: true,
data: {
imageUrl: imageUrl,
text: params.text,
size: params.size,
level: params.level,
format: 'image'
}
};
} else {
// 如果返回的不是图片尝试解析JSON
const jsonData = await response.json();
if (jsonData.code === 0 && jsonData.data && jsonData.data.data_uri) {
return {
success: true,
data: {
imageUrl: jsonData.data.data_uri,
text: params.text,
size: params.size,
level: params.level,
format: 'json',
base64: jsonData.data.base64,
mimeType: jsonData.data.mime_type
}
};
} else {
throw new Error(jsonData.message || '生成失败');
}
}
} else {
// JSON或text格式
const jsonData = await response.json();
if (jsonData.code === 0 && jsonData.data) {
return {
success: true,
data: {
imageUrl: jsonData.data.data_uri,
text: params.text,
size: params.size,
level: params.level,
format: params.encoding,
base64: jsonData.data.base64,
mimeType: jsonData.data.mime_type
}
};
} else {
throw new Error(jsonData.message || '生成失败');
}
}
} catch (error) {
clearTimeout(timeoutId);
if (error.name === 'AbortError') {
throw new Error('请求超时,请重试');
}
throw error;
}
}
// 显示加载状态
showLoading() {
this.hideAllStates();
document.getElementById('loading').classList.remove('hidden');
const btn = document.querySelector('.generate-btn');
btn.classList.add('loading');
btn.disabled = true;
}
// 显示错误
showError(message) {
this.hideAllStates();
const errorDiv = document.getElementById('error');
const errorMessage = errorDiv.querySelector('.error-message');
errorMessage.textContent = message;
errorDiv.classList.remove('hidden');
this.resetButton();
}
// 显示结果
showResult(data, params) {
this.hideAllStates();
const resultDiv = document.getElementById('result');
const qrImage = document.getElementById('qrImage');
const resultText = document.querySelector('.result-text');
qrImage.src = data.imageUrl;
qrImage.alt = `二维码: ${data.text}`;
resultText.innerHTML = `
<strong>内容:</strong> ${this.escapeHtml(data.text)}<br>
<strong>尺寸:</strong> ${data.size}x${data.size}<br>
<strong>容错级别:</strong> ${data.level}<br>
<strong>格式:</strong> ${data.format.toUpperCase()}
`;
resultDiv.classList.remove('hidden');
this.resetButton();
// 保存数据供下载使用
this.currentQRData = data;
}
// 隐藏所有状态
hideAllStates() {
document.getElementById('loading').classList.add('hidden');
document.getElementById('error').classList.add('hidden');
document.getElementById('result').classList.add('hidden');
}
// 重置按钮状态
resetButton() {
const btn = document.querySelector('.generate-btn');
btn.classList.remove('loading');
btn.disabled = false;
}
// 重试生成
async retryGeneration() {
const form = document.getElementById('qrForm');
const formData = new FormData(form);
const params = {
text: formData.get('text').trim(),
size: formData.get('size'),
level: formData.get('level'),
encoding: formData.get('encoding')
};
this.showLoading();
await this.generateQRCode(params);
}
// 下载二维码
downloadQRCode() {
if (!this.currentQRData) return;
const link = document.createElement('a');
link.href = this.currentQRData.imageUrl;
link.download = `qrcode_${Date.now()}.png`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
this.showToast('二维码已下载');
}
// 复制图片链接
async copyImageLink() {
if (!this.currentQRData) return;
try {
await navigator.clipboard.writeText(this.currentQRData.imageUrl);
this.showToast('链接已复制到剪贴板');
} catch (error) {
console.error('复制失败:', error);
this.showToast('复制失败,请手动复制');
}
}
// 重置表单
resetForm() {
document.getElementById('qrForm').reset();
this.hideAllStates();
this.currentQRData = null;
// 清除字符计数
const counter = document.querySelector('.char-counter');
if (counter) counter.remove();
// 清除字段错误
document.querySelectorAll('input, textarea, select').forEach(field => {
this.clearFieldError(field);
});
// 聚焦到文本框
document.getElementById('text').focus();
}
// 显示提示消息
showToast(message) {
// 移除旧的toast
const oldToast = document.querySelector('.toast');
if (oldToast) oldToast.remove();
const toast = document.createElement('div');
toast.className = 'toast';
toast.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: #4caf50;
color: white;
padding: 12px 20px;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
z-index: 1000;
animation: slideIn 0.3s ease;
`;
toast.textContent = message;
document.body.appendChild(toast);
setTimeout(() => {
toast.style.animation = 'slideOut 0.3s ease';
setTimeout(() => toast.remove(), 300);
}, 3000);
}
// HTML转义
escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
}
// 添加CSS动画
const style = document.createElement('style');
style.textContent = `
@keyframes slideIn {
from { transform: translateX(100%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
@keyframes slideOut {
from { transform: translateX(0); opacity: 1; }
to { transform: translateX(100%); opacity: 0; }
}
`;
document.head.appendChild(style);
// 页面加载完成后初始化
document.addEventListener('DOMContentLoaded', () => {
new QRCodeGenerator();
});
// 错误处理
window.addEventListener('error', (e) => {
console.error('全局错误:', e.error);
});
window.addEventListener('unhandledrejection', (e) => {
console.error('未处理的Promise拒绝:', e.reason);
});

View File

@@ -1,3 +1,3 @@
[
"https://60s.api.shumengya.top"
]
[
"https://60s.api.shumengya.top"
]

View File

@@ -1,192 +1,192 @@
/* 彩虹渐变背景样式 */
/* 主背景渐变 */
body {
background: linear-gradient(
135deg,
rgba(255, 107, 107, 0.3) 0%,
rgba(255, 165, 0, 0.3) 14.28%,
rgba(255, 255, 0, 0.25) 28.56%,
rgba(50, 205, 50, 0.3) 42.84%,
rgba(0, 191, 255, 0.3) 57.12%,
rgba(65, 105, 225, 0.3) 71.4%,
rgba(147, 112, 219, 0.3) 85.68%,
rgba(255, 105, 180, 0.3) 100%
);
background-size: 400% 400%;
animation: rainbowShift 20s ease infinite;
min-height: 100vh;
}
/* 彩虹渐变动画 */
@keyframes rainbowShift {
0% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
}
/* 半透明覆盖层,增强可读性 */
body::before {
content: '';
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(255, 255, 255, 0.4);
backdrop-filter: blur(2px);
z-index: -1;
pointer-events: none;
}
/* 搜索按钮彩虹渐变 */
.search-btn {
background: linear-gradient(
45deg,
rgba(255, 107, 107, 0.8),
rgba(255, 165, 0, 0.8),
rgba(255, 255, 0, 0.7),
rgba(50, 205, 50, 0.8),
rgba(0, 191, 255, 0.8),
rgba(65, 105, 225, 0.8),
rgba(147, 112, 219, 0.8)
);
background-size: 300% 300%;
animation: buttonRainbow 12s ease infinite;
}
@keyframes buttonRainbow {
0%, 100% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
}
/* 结果卡片边框彩虹渐变 */
.result-card {
position: relative;
overflow: hidden;
}
.result-card::before {
content: '';
position: absolute;
top: -2px;
left: -2px;
right: -2px;
bottom: -2px;
background: linear-gradient(
45deg,
rgba(255, 107, 107, 0.4),
rgba(255, 165, 0, 0.4),
rgba(255, 255, 0, 0.3),
rgba(50, 205, 50, 0.4),
rgba(0, 191, 255, 0.4),
rgba(65, 105, 225, 0.4),
rgba(147, 112, 219, 0.4),
rgba(255, 107, 107, 0.4)
);
background-size: 400% 400%;
animation: borderRainbow 15s linear infinite;
border-radius: inherit;
z-index: -1;
}
@keyframes borderRainbow {
0% {
background-position: 0% 50%;
}
100% {
background-position: 400% 50%;
}
}
/* 加载动画彩虹效果 */
.loading-spinner {
border: 4px solid rgba(255, 255, 255, 0.3);
border-top: 4px solid transparent;
border-image: linear-gradient(
45deg,
#ff6b6b,
#ffa500,
#ffff00,
#32cd32,
#00bfff,
#4169e1,
#9370db
) 1;
animation: spin 1s linear infinite, colorShift 3s ease infinite;
}
@keyframes colorShift {
0%, 100% {
filter: hue-rotate(0deg);
}
50% {
filter: hue-rotate(180deg);
}
}
/* 链接悬停彩虹效果 */
.result-link:hover {
background: linear-gradient(
90deg,
rgba(255, 107, 107, 0.7),
rgba(255, 165, 0, 0.7),
rgba(255, 255, 0, 0.6),
rgba(50, 205, 50, 0.7),
rgba(0, 191, 255, 0.7),
rgba(65, 105, 225, 0.7),
rgba(147, 112, 219, 0.7)
);
background-size: 200% 200%;
animation: linkRainbow 3s ease infinite;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
@keyframes linkRainbow {
0%, 100% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
}
/* 标题彩虹文字效果 */
.title {
background: linear-gradient(
90deg,
rgba(255, 107, 107, 0.8),
rgba(255, 165, 0, 0.8),
rgba(255, 255, 0, 0.7),
rgba(50, 205, 50, 0.8),
rgba(0, 191, 255, 0.8),
rgba(65, 105, 225, 0.8),
rgba(147, 112, 219, 0.8)
);
background-size: 200% 200%;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
animation: titleRainbow 8s ease infinite;
}
@keyframes titleRainbow {
0%, 100% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
/* 彩虹渐变背景样式 */
/* 主背景渐变 */
body {
background: linear-gradient(
135deg,
rgba(255, 107, 107, 0.3) 0%,
rgba(255, 165, 0, 0.3) 14.28%,
rgba(255, 255, 0, 0.25) 28.56%,
rgba(50, 205, 50, 0.3) 42.84%,
rgba(0, 191, 255, 0.3) 57.12%,
rgba(65, 105, 225, 0.3) 71.4%,
rgba(147, 112, 219, 0.3) 85.68%,
rgba(255, 105, 180, 0.3) 100%
);
background-size: 400% 400%;
animation: rainbowShift 20s ease infinite;
min-height: 100vh;
}
/* 彩虹渐变动画 */
@keyframes rainbowShift {
0% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
}
/* 半透明覆盖层,增强可读性 */
body::before {
content: '';
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(255, 255, 255, 0.4);
backdrop-filter: blur(2px);
z-index: -1;
pointer-events: none;
}
/* 搜索按钮彩虹渐变 */
.search-btn {
background: linear-gradient(
45deg,
rgba(255, 107, 107, 0.8),
rgba(255, 165, 0, 0.8),
rgba(255, 255, 0, 0.7),
rgba(50, 205, 50, 0.8),
rgba(0, 191, 255, 0.8),
rgba(65, 105, 225, 0.8),
rgba(147, 112, 219, 0.8)
);
background-size: 300% 300%;
animation: buttonRainbow 12s ease infinite;
}
@keyframes buttonRainbow {
0%, 100% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
}
/* 结果卡片边框彩虹渐变 */
.result-card {
position: relative;
overflow: hidden;
}
.result-card::before {
content: '';
position: absolute;
top: -2px;
left: -2px;
right: -2px;
bottom: -2px;
background: linear-gradient(
45deg,
rgba(255, 107, 107, 0.4),
rgba(255, 165, 0, 0.4),
rgba(255, 255, 0, 0.3),
rgba(50, 205, 50, 0.4),
rgba(0, 191, 255, 0.4),
rgba(65, 105, 225, 0.4),
rgba(147, 112, 219, 0.4),
rgba(255, 107, 107, 0.4)
);
background-size: 400% 400%;
animation: borderRainbow 15s linear infinite;
border-radius: inherit;
z-index: -1;
}
@keyframes borderRainbow {
0% {
background-position: 0% 50%;
}
100% {
background-position: 400% 50%;
}
}
/* 加载动画彩虹效果 */
.loading-spinner {
border: 4px solid rgba(255, 255, 255, 0.3);
border-top: 4px solid transparent;
border-image: linear-gradient(
45deg,
#ff6b6b,
#ffa500,
#ffff00,
#32cd32,
#00bfff,
#4169e1,
#9370db
) 1;
animation: spin 1s linear infinite, colorShift 3s ease infinite;
}
@keyframes colorShift {
0%, 100% {
filter: hue-rotate(0deg);
}
50% {
filter: hue-rotate(180deg);
}
}
/* 链接悬停彩虹效果 */
.result-link:hover {
background: linear-gradient(
90deg,
rgba(255, 107, 107, 0.7),
rgba(255, 165, 0, 0.7),
rgba(255, 255, 0, 0.6),
rgba(50, 205, 50, 0.7),
rgba(0, 191, 255, 0.7),
rgba(65, 105, 225, 0.7),
rgba(147, 112, 219, 0.7)
);
background-size: 200% 200%;
animation: linkRainbow 3s ease infinite;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
@keyframes linkRainbow {
0%, 100% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
}
/* 标题彩虹文字效果 */
.title {
background: linear-gradient(
90deg,
rgba(255, 107, 107, 0.8),
rgba(255, 165, 0, 0.8),
rgba(255, 255, 0, 0.7),
rgba(50, 205, 50, 0.8),
rgba(0, 191, 255, 0.8),
rgba(65, 105, 225, 0.8),
rgba(147, 112, 219, 0.8)
);
background-size: 200% 200%;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
animation: titleRainbow 8s ease infinite;
}
@keyframes titleRainbow {
0%, 100% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
}

View File

@@ -1,83 +1,83 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>百度百科词条查询</title>
<link rel="stylesheet" href="css/background.css">
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<div class="container">
<header class="header">
<h1 class="title">百度百科词条查询</h1>
<p class="subtitle">探索知识的彩虹世界</p>
</header>
<main class="main">
<div class="search-section">
<div class="search-container">
<input type="text" id="searchInput" class="search-input" placeholder="请输入要查询的词条..." autocomplete="off">
<button id="searchBtn" class="search-btn">
<span class="search-icon">🔍</span>
<span class="search-text">搜索</span>
</button>
</div>
</div>
<div class="result-section" id="resultSection">
<div class="loading" id="loading" style="display: none;">
<div class="loading-spinner"></div>
<p>正在搜索中...</p>
</div>
<div class="result-card" id="resultCard" style="display: none;">
<div class="result-header">
<h2 class="result-title" id="resultTitle"></h2>
<p class="result-description" id="resultDescription"></p>
</div>
<div class="result-content">
<div class="result-image-container">
<img id="resultImage" class="result-image" alt="词条图片">
</div>
<div class="result-text">
<div class="result-abstract">
<h3>摘要</h3>
<p id="resultAbstract"></p>
</div>
<div class="result-actions">
<a id="resultLink" class="result-link" target="_blank">
<span>查看完整词条</span>
<span class="link-icon"></span>
</a>
</div>
</div>
</div>
</div>
<div class="error-message" id="errorMessage" style="display: none;">
<div class="error-icon">⚠️</div>
<h3>查询失败</h3>
<p id="errorText"></p>
<button id="retryBtn" class="retry-btn">重试</button>
</div>
<div class="welcome-message" id="welcomeMessage">
<div class="welcome-icon">📚</div>
<h3>欢迎使用百度百科词条查询</h3>
<p>在上方搜索框中输入您想了解的词条,开始探索知识的海洋</p>
</div>
</div>
</main>
<footer class="footer">
<p>数据来源:百度百科</p>
</footer>
</div>
<script src="js/script.js"></script>
</body>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>百度百科词条查询</title>
<link rel="stylesheet" href="css/background.css">
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<div class="container">
<header class="header">
<h1 class="title">百度百科词条查询</h1>
<p class="subtitle">探索知识的彩虹世界</p>
</header>
<main class="main">
<div class="search-section">
<div class="search-container">
<input type="text" id="searchInput" class="search-input" placeholder="请输入要查询的词条..." autocomplete="off">
<button id="searchBtn" class="search-btn">
<span class="search-icon">🔍</span>
<span class="search-text">搜索</span>
</button>
</div>
</div>
<div class="result-section" id="resultSection">
<div class="loading" id="loading" style="display: none;">
<div class="loading-spinner"></div>
<p>正在搜索中...</p>
</div>
<div class="result-card" id="resultCard" style="display: none;">
<div class="result-header">
<h2 class="result-title" id="resultTitle"></h2>
<p class="result-description" id="resultDescription"></p>
</div>
<div class="result-content">
<div class="result-image-container">
<img id="resultImage" class="result-image" alt="词条图片">
</div>
<div class="result-text">
<div class="result-abstract">
<h3>摘要</h3>
<p id="resultAbstract"></p>
</div>
<div class="result-actions">
<a id="resultLink" class="result-link" target="_blank">
<span>查看完整词条</span>
<span class="link-icon"></span>
</a>
</div>
</div>
</div>
</div>
<div class="error-message" id="errorMessage" style="display: none;">
<div class="error-icon">⚠️</div>
<h3>查询失败</h3>
<p id="errorText"></p>
<button id="retryBtn" class="retry-btn">重试</button>
</div>
<div class="welcome-message" id="welcomeMessage">
<div class="welcome-icon">📚</div>
<h3>欢迎使用百度百科词条查询</h3>
<p>在上方搜索框中输入您想了解的词条,开始探索知识的海洋</p>
</div>
</div>
</main>
<footer class="footer">
<p>数据来源:百度百科</p>
</footer>
</div>
<script src="js/script.js"></script>
</body>
</html>

View File

@@ -1,320 +1,320 @@
// 百度百科词条查询应用
class BaikeApp {
constructor() {
// API接口列表
this.apiEndpoints = [
'https://60s.api.shumengya.top',
];
this.currentApiIndex = 0;
this.isLoading = false;
this.initElements();
this.bindEvents();
}
// 初始化DOM元素
initElements() {
this.searchInput = document.getElementById('searchInput');
this.searchBtn = document.getElementById('searchBtn');
this.resultSection = document.getElementById('resultSection');
this.loading = document.getElementById('loading');
this.resultCard = document.getElementById('resultCard');
this.errorMessage = document.getElementById('errorMessage');
this.welcomeMessage = document.getElementById('welcomeMessage');
this.retryBtn = document.getElementById('retryBtn');
// 结果显示元素
this.resultTitle = document.getElementById('resultTitle');
this.resultDescription = document.getElementById('resultDescription');
this.resultImage = document.getElementById('resultImage');
this.resultAbstract = document.getElementById('resultAbstract');
this.resultLink = document.getElementById('resultLink');
this.errorText = document.getElementById('errorText');
}
// 绑定事件
bindEvents() {
// 搜索按钮点击事件
this.searchBtn.addEventListener('click', () => {
this.handleSearch();
});
// 输入框回车事件
this.searchInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
this.handleSearch();
}
});
// 重试按钮事件
this.retryBtn.addEventListener('click', () => {
this.handleSearch();
});
// 输入框焦点事件
this.searchInput.addEventListener('focus', () => {
this.searchInput.select();
});
}
// 处理搜索
async handleSearch() {
const query = this.searchInput.value.trim();
if (!query) {
this.showError('请输入要查询的词条');
this.searchInput.focus();
return;
}
if (this.isLoading) {
return;
}
await this.searchBaike(query);
}
// 搜索百科词条
async searchBaike(query) {
this.showLoading();
this.isLoading = true;
// 重置API索引
this.currentApiIndex = 0;
const success = await this.tryApiCall(query);
if (!success) {
this.showError('所有API接口都无法访问请稍后重试');
}
this.isLoading = false;
}
// 尝试API调用
async tryApiCall(query) {
for (let i = 0; i < this.apiEndpoints.length; i++) {
const endpoint = this.apiEndpoints[this.currentApiIndex];
try {
const result = await this.callApi(endpoint, query);
if (result) {
this.showResult(result);
return true;
}
} catch (error) {
console.warn(`API ${endpoint} 调用失败:`, error.message);
}
// 切换到下一个API
this.currentApiIndex = (this.currentApiIndex + 1) % this.apiEndpoints.length;
}
return false;
}
// 调用API
async callApi(endpoint, query) {
const url = `${endpoint}/v2/baike?word=${encodeURIComponent(query)}`;
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 10000); // 10秒超时
try {
const response = await fetch(url, {
method: 'GET',
signal: controller.signal,
headers: {
'Accept': 'application/json',
}
});
clearTimeout(timeoutId);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
if (data.code === 200 && data.data) {
return data.data;
} else {
throw new Error(data.message || '未找到相关词条');
}
} catch (error) {
clearTimeout(timeoutId);
if (error.name === 'AbortError') {
throw new Error('请求超时');
}
throw error;
}
}
// 显示加载状态
showLoading() {
this.hideAllSections();
this.loading.style.display = 'flex';
}
// 显示搜索结果
showResult(data) {
this.hideAllSections();
// 填充数据
this.resultTitle.textContent = data.title || '未知标题';
this.resultDescription.textContent = data.description || '暂无描述';
this.resultAbstract.textContent = data.abstract || '暂无摘要信息';
// 处理图片
if (data.cover) {
this.resultImage.src = data.cover;
this.resultImage.style.display = 'block';
this.resultImage.onerror = () => {
this.resultImage.style.display = 'none';
};
} else {
this.resultImage.style.display = 'none';
}
// 处理链接
if (data.link) {
this.resultLink.href = data.link;
this.resultLink.style.display = 'inline-flex';
} else {
this.resultLink.style.display = 'none';
}
this.resultCard.style.display = 'block';
// 滚动到结果区域
this.resultCard.scrollIntoView({
behavior: 'smooth',
block: 'start'
});
}
// 显示错误信息
showError(message) {
this.hideAllSections();
this.errorText.textContent = message;
this.errorMessage.style.display = 'flex';
}
// 隐藏所有区域
hideAllSections() {
this.loading.style.display = 'none';
this.resultCard.style.display = 'none';
this.errorMessage.style.display = 'none';
this.welcomeMessage.style.display = 'none';
}
// 显示欢迎信息
showWelcome() {
this.hideAllSections();
this.welcomeMessage.style.display = 'flex';
}
}
// 工具函数
class Utils {
// 防抖函数
static debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// 节流函数
static throttle(func, limit) {
let inThrottle;
return function() {
const args = arguments;
const context = this;
if (!inThrottle) {
func.apply(context, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
// 格式化文本长度
static truncateText(text, maxLength) {
if (text.length <= maxLength) {
return text;
}
return text.substring(0, maxLength) + '...';
}
// 检查是否为移动设备
static isMobile() {
return window.innerWidth <= 768;
}
}
// 页面加载完成后初始化应用
document.addEventListener('DOMContentLoaded', () => {
// 初始化应用
const app = new BaikeApp();
// 添加页面可见性变化监听
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'visible') {
// 页面重新可见时,聚焦搜索框
if (!app.isLoading) {
app.searchInput.focus();
}
}
});
// 添加窗口大小变化监听
window.addEventListener('resize', Utils.throttle(() => {
// 响应式调整
if (Utils.isMobile()) {
// 移动端特殊处理
document.body.classList.add('mobile');
} else {
document.body.classList.remove('mobile');
}
}, 250));
// 初始检查设备类型
if (Utils.isMobile()) {
document.body.classList.add('mobile');
}
// 页面加载完成后聚焦搜索框
setTimeout(() => {
app.searchInput.focus();
}, 500);
// 添加键盘快捷键支持
document.addEventListener('keydown', (e) => {
// Ctrl/Cmd + K 聚焦搜索框
if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
e.preventDefault();
app.searchInput.focus();
app.searchInput.select();
}
// ESC 清空搜索框
if (e.key === 'Escape') {
app.searchInput.value = '';
app.showWelcome();
app.searchInput.focus();
}
});
console.log('百度百科词条查询应用已初始化');
// 百度百科词条查询应用
class BaikeApp {
constructor() {
// API接口列表
this.apiEndpoints = [
'https://60s.api.shumengya.top',
];
this.currentApiIndex = 0;
this.isLoading = false;
this.initElements();
this.bindEvents();
}
// 初始化DOM元素
initElements() {
this.searchInput = document.getElementById('searchInput');
this.searchBtn = document.getElementById('searchBtn');
this.resultSection = document.getElementById('resultSection');
this.loading = document.getElementById('loading');
this.resultCard = document.getElementById('resultCard');
this.errorMessage = document.getElementById('errorMessage');
this.welcomeMessage = document.getElementById('welcomeMessage');
this.retryBtn = document.getElementById('retryBtn');
// 结果显示元素
this.resultTitle = document.getElementById('resultTitle');
this.resultDescription = document.getElementById('resultDescription');
this.resultImage = document.getElementById('resultImage');
this.resultAbstract = document.getElementById('resultAbstract');
this.resultLink = document.getElementById('resultLink');
this.errorText = document.getElementById('errorText');
}
// 绑定事件
bindEvents() {
// 搜索按钮点击事件
this.searchBtn.addEventListener('click', () => {
this.handleSearch();
});
// 输入框回车事件
this.searchInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
this.handleSearch();
}
});
// 重试按钮事件
this.retryBtn.addEventListener('click', () => {
this.handleSearch();
});
// 输入框焦点事件
this.searchInput.addEventListener('focus', () => {
this.searchInput.select();
});
}
// 处理搜索
async handleSearch() {
const query = this.searchInput.value.trim();
if (!query) {
this.showError('请输入要查询的词条');
this.searchInput.focus();
return;
}
if (this.isLoading) {
return;
}
await this.searchBaike(query);
}
// 搜索百科词条
async searchBaike(query) {
this.showLoading();
this.isLoading = true;
// 重置API索引
this.currentApiIndex = 0;
const success = await this.tryApiCall(query);
if (!success) {
this.showError('所有API接口都无法访问请稍后重试');
}
this.isLoading = false;
}
// 尝试API调用
async tryApiCall(query) {
for (let i = 0; i < this.apiEndpoints.length; i++) {
const endpoint = this.apiEndpoints[this.currentApiIndex];
try {
const result = await this.callApi(endpoint, query);
if (result) {
this.showResult(result);
return true;
}
} catch (error) {
console.warn(`API ${endpoint} 调用失败:`, error.message);
}
// 切换到下一个API
this.currentApiIndex = (this.currentApiIndex + 1) % this.apiEndpoints.length;
}
return false;
}
// 调用API
async callApi(endpoint, query) {
const url = `${endpoint}/v2/baike?word=${encodeURIComponent(query)}`;
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 10000); // 10秒超时
try {
const response = await fetch(url, {
method: 'GET',
signal: controller.signal,
headers: {
'Accept': 'application/json',
}
});
clearTimeout(timeoutId);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
if (data.code === 200 && data.data) {
return data.data;
} else {
throw new Error(data.message || '未找到相关词条');
}
} catch (error) {
clearTimeout(timeoutId);
if (error.name === 'AbortError') {
throw new Error('请求超时');
}
throw error;
}
}
// 显示加载状态
showLoading() {
this.hideAllSections();
this.loading.style.display = 'flex';
}
// 显示搜索结果
showResult(data) {
this.hideAllSections();
// 填充数据
this.resultTitle.textContent = data.title || '未知标题';
this.resultDescription.textContent = data.description || '暂无描述';
this.resultAbstract.textContent = data.abstract || '暂无摘要信息';
// 处理图片
if (data.cover) {
this.resultImage.src = data.cover;
this.resultImage.style.display = 'block';
this.resultImage.onerror = () => {
this.resultImage.style.display = 'none';
};
} else {
this.resultImage.style.display = 'none';
}
// 处理链接
if (data.link) {
this.resultLink.href = data.link;
this.resultLink.style.display = 'inline-flex';
} else {
this.resultLink.style.display = 'none';
}
this.resultCard.style.display = 'block';
// 滚动到结果区域
this.resultCard.scrollIntoView({
behavior: 'smooth',
block: 'start'
});
}
// 显示错误信息
showError(message) {
this.hideAllSections();
this.errorText.textContent = message;
this.errorMessage.style.display = 'flex';
}
// 隐藏所有区域
hideAllSections() {
this.loading.style.display = 'none';
this.resultCard.style.display = 'none';
this.errorMessage.style.display = 'none';
this.welcomeMessage.style.display = 'none';
}
// 显示欢迎信息
showWelcome() {
this.hideAllSections();
this.welcomeMessage.style.display = 'flex';
}
}
// 工具函数
class Utils {
// 防抖函数
static debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// 节流函数
static throttle(func, limit) {
let inThrottle;
return function() {
const args = arguments;
const context = this;
if (!inThrottle) {
func.apply(context, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
// 格式化文本长度
static truncateText(text, maxLength) {
if (text.length <= maxLength) {
return text;
}
return text.substring(0, maxLength) + '...';
}
// 检查是否为移动设备
static isMobile() {
return window.innerWidth <= 768;
}
}
// 页面加载完成后初始化应用
document.addEventListener('DOMContentLoaded', () => {
// 初始化应用
const app = new BaikeApp();
// 添加页面可见性变化监听
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'visible') {
// 页面重新可见时,聚焦搜索框
if (!app.isLoading) {
app.searchInput.focus();
}
}
});
// 添加窗口大小变化监听
window.addEventListener('resize', Utils.throttle(() => {
// 响应式调整
if (Utils.isMobile()) {
// 移动端特殊处理
document.body.classList.add('mobile');
} else {
document.body.classList.remove('mobile');
}
}, 250));
// 初始检查设备类型
if (Utils.isMobile()) {
document.body.classList.add('mobile');
}
// 页面加载完成后聚焦搜索框
setTimeout(() => {
app.searchInput.focus();
}, 500);
// 添加键盘快捷键支持
document.addEventListener('keydown', (e) => {
// Ctrl/Cmd + K 聚焦搜索框
if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
e.preventDefault();
app.searchInput.focus();
app.searchInput.select();
}
// ESC 清空搜索框
if (e.key === 'Escape') {
app.searchInput.value = '';
app.showWelcome();
app.searchInput.focus();
}
});
console.log('百度百科词条查询应用已初始化');
});

View File

@@ -1,3 +1,3 @@
[
"https://60s.api.shumengya.top"
]
[
"https://60s.api.shumengya.top"
]

View File

@@ -1,12 +1,12 @@
{
"code": 200,
"message": "获取成功。数据来自官方/权威源头,以确保稳定与实时。开源地址 https://github.com/vikiboss/60s反馈群 595941841",
"data": {
"title": "西游记",
"description": "明代吴承恩创作的章回体长篇神魔小说",
"abstract": "《西游记》又名《西游释厄传》,是中国古代第一部浪漫主义章回体长篇神魔小说。最早的《西游记》版本是明代万历二十年金陵世德堂《新刻出像官板大字西游记》,未署作者姓名。鲁迅、董作宾等人根据《淮安府志》“吴承恩《西游记》”的记载予以最终论定“吴承恩原著”。该小说主要讲述了孙悟空出世,并寻菩提祖师学艺及大闹天宫后,与猪八戒、沙僧和白龙马一同护送唐僧西天取经,于路上历经险阻,降妖除魔,渡过了九九八十一难,成功...",
"cover": "https://bkimg.cdn.bcebos.com/pic/b7fd5266d01609248d763e43db0735fae6cd3412?x-bce-process=image/format,f_auto",
"has_other": true,
"link": "http://baike.baidu.com/subview/2583/5315045.htm"
}
{
"code": 200,
"message": "获取成功。数据来自官方/权威源头,以确保稳定与实时。开源地址 https://github.com/vikiboss/60s反馈群 595941841",
"data": {
"title": "西游记",
"description": "明代吴承恩创作的章回体长篇神魔小说",
"abstract": "《西游记》又名《西游释厄传》,是中国古代第一部浪漫主义章回体长篇神魔小说。最早的《西游记》版本是明代万历二十年金陵世德堂《新刻出像官板大字西游记》,未署作者姓名。鲁迅、董作宾等人根据《淮安府志》“吴承恩《西游记》”的记载予以最终论定“吴承恩原著”。该小说主要讲述了孙悟空出世,并寻菩提祖师学艺及大闹天宫后,与猪八戒、沙僧和白龙马一同护送唐僧西天取经,于路上历经险阻,降妖除魔,渡过了九九八十一难,成功...",
"cover": "https://bkimg.cdn.bcebos.com/pic/b7fd5266d01609248d763e43db0735fae6cd3412?x-bce-process=image/format,f_auto",
"has_other": true,
"link": "http://baike.baidu.com/subview/2583/5315045.htm"
}
}

View File

@@ -1,243 +1,243 @@
/* 背景样式文件 - 独立管理背景相关样式 */
/* 主体背景 */
body {
background: linear-gradient(135deg, #e8f5e8 0%, #f0f8f0 25%, #e1f5e1 50%, #f5f9f5 75%, #e8f5e8 100%);
background-size: 400% 400%;
animation: gradientShift 15s ease infinite;
position: relative;
overflow-x: hidden;
}
/* 背景动画 */
@keyframes gradientShift {
0% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
}
/* 装饰性背景元素 */
body::before {
content: '';
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image:
radial-gradient(circle at 20% 80%, rgba(144, 238, 144, 0.1) 0%, transparent 50%),
radial-gradient(circle at 80% 20%, rgba(152, 251, 152, 0.1) 0%, transparent 50%),
radial-gradient(circle at 40% 40%, rgba(173, 255, 173, 0.08) 0%, transparent 50%);
pointer-events: none;
z-index: -2;
}
/* 浮动装饰圆点 */
body::after {
content: '';
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image:
radial-gradient(2px 2px at 20px 30px, rgba(76, 175, 80, 0.3), transparent),
radial-gradient(2px 2px at 40px 70px, rgba(129, 199, 132, 0.3), transparent),
radial-gradient(1px 1px at 90px 40px, rgba(165, 214, 167, 0.3), transparent),
radial-gradient(1px 1px at 130px 80px, rgba(200, 230, 201, 0.3), transparent),
radial-gradient(2px 2px at 160px 30px, rgba(76, 175, 80, 0.2), transparent);
background-repeat: repeat;
background-size: 200px 100px;
animation: floatDots 20s linear infinite;
pointer-events: none;
z-index: -1;
}
@keyframes floatDots {
0% {
transform: translateY(0px);
}
100% {
transform: translateY(-100px);
}
}
/* 容器背景增强 */
.container {
background: rgba(255, 255, 255, 0.02);
backdrop-filter: blur(10px);
border-radius: 20px;
position: relative;
}
/* 表单区域背景 */
.form-section {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20px);
border: 1px solid rgba(144, 238, 144, 0.3);
position: relative;
overflow: hidden;
}
.form-section::before {
content: '';
position: absolute;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
background: linear-gradient(45deg, transparent, rgba(144, 238, 144, 0.05), transparent);
animation: shimmer 3s ease-in-out infinite;
pointer-events: none;
}
@keyframes shimmer {
0% {
transform: translateX(-100%) translateY(-100%) rotate(45deg);
}
50% {
transform: translateX(100%) translateY(100%) rotate(45deg);
}
100% {
transform: translateX(-100%) translateY(-100%) rotate(45deg);
}
}
/* 结果卡片背景 */
.basic-info-card,
.bmi-card,
.weight-card,
.metabolism-card,
.body-fat-card,
.measurements-card,
.advice-card {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(15px);
border: 1px solid rgba(144, 238, 144, 0.2);
position: relative;
overflow: hidden;
}
/* 卡片悬停背景效果 */
.basic-info-card:hover,
.bmi-card:hover,
.weight-card:hover,
.metabolism-card:hover,
.body-fat-card:hover,
.measurements-card:hover,
.advice-card:hover {
background: rgba(255, 255, 255, 0.98);
border-color: rgba(76, 175, 80, 0.4);
}
/* 免责声明卡片背景 */
.disclaimer-card {
background: rgba(255, 243, 205, 0.95);
backdrop-filter: blur(15px);
border: 1px solid rgba(255, 234, 167, 0.5);
}
/* 错误区域背景 */
.error-content {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20px);
border: 1px solid rgba(220, 53, 69, 0.2);
}
/* 输入框背景 */
.form-input,
.form-select {
background: rgba(248, 255, 248, 0.9);
backdrop-filter: blur(10px);
}
.form-input:focus,
.form-select:focus {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(15px);
}
/* 信息项背景 */
.info-item {
background: rgba(248, 255, 248, 0.8);
backdrop-filter: blur(5px);
}
/* BMI分类背景 */
.bmi-category {
background: rgba(232, 245, 232, 0.9);
backdrop-filter: blur(10px);
}
/* 健康建议列表项背景 */
.health-tips li {
background: rgba(248, 255, 248, 0.8);
backdrop-filter: blur(5px);
}
/* 按钮背景增强 */
.submit-btn {
position: relative;
overflow: hidden;
}
.submit-btn::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
transition: left 0.5s;
}
.submit-btn:hover::before {
left: 100%;
}
/* 重置按钮背景 */
.reset-btn {
background: rgba(232, 245, 232, 0.9);
backdrop-filter: blur(10px);
}
.reset-btn:hover {
background: rgba(212, 237, 218, 0.95);
}
/* 响应式背景调整 */
@media (max-width: 767px) {
body::after {
background-size: 150px 75px;
animation-duration: 15s;
}
.form-section::before {
animation-duration: 2s;
}
}
@media (min-width: 768px) and (max-width: 1024px) {
body::after {
background-size: 180px 90px;
animation-duration: 18s;
}
}
@media (min-width: 1024px) {
body::after {
background-size: 220px 110px;
animation-duration: 25s;
}
.container {
background: rgba(255, 255, 255, 0.05);
}
/* 背景样式文件 - 独立管理背景相关样式 */
/* 主体背景 */
body {
background: linear-gradient(135deg, #e8f5e8 0%, #f0f8f0 25%, #e1f5e1 50%, #f5f9f5 75%, #e8f5e8 100%);
background-size: 400% 400%;
animation: gradientShift 15s ease infinite;
position: relative;
overflow-x: hidden;
}
/* 背景动画 */
@keyframes gradientShift {
0% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
}
/* 装饰性背景元素 */
body::before {
content: '';
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image:
radial-gradient(circle at 20% 80%, rgba(144, 238, 144, 0.1) 0%, transparent 50%),
radial-gradient(circle at 80% 20%, rgba(152, 251, 152, 0.1) 0%, transparent 50%),
radial-gradient(circle at 40% 40%, rgba(173, 255, 173, 0.08) 0%, transparent 50%);
pointer-events: none;
z-index: -2;
}
/* 浮动装饰圆点 */
body::after {
content: '';
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image:
radial-gradient(2px 2px at 20px 30px, rgba(76, 175, 80, 0.3), transparent),
radial-gradient(2px 2px at 40px 70px, rgba(129, 199, 132, 0.3), transparent),
radial-gradient(1px 1px at 90px 40px, rgba(165, 214, 167, 0.3), transparent),
radial-gradient(1px 1px at 130px 80px, rgba(200, 230, 201, 0.3), transparent),
radial-gradient(2px 2px at 160px 30px, rgba(76, 175, 80, 0.2), transparent);
background-repeat: repeat;
background-size: 200px 100px;
animation: floatDots 20s linear infinite;
pointer-events: none;
z-index: -1;
}
@keyframes floatDots {
0% {
transform: translateY(0px);
}
100% {
transform: translateY(-100px);
}
}
/* 容器背景增强 */
.container {
background: rgba(255, 255, 255, 0.02);
backdrop-filter: blur(10px);
border-radius: 20px;
position: relative;
}
/* 表单区域背景 */
.form-section {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20px);
border: 1px solid rgba(144, 238, 144, 0.3);
position: relative;
overflow: hidden;
}
.form-section::before {
content: '';
position: absolute;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
background: linear-gradient(45deg, transparent, rgba(144, 238, 144, 0.05), transparent);
animation: shimmer 3s ease-in-out infinite;
pointer-events: none;
}
@keyframes shimmer {
0% {
transform: translateX(-100%) translateY(-100%) rotate(45deg);
}
50% {
transform: translateX(100%) translateY(100%) rotate(45deg);
}
100% {
transform: translateX(-100%) translateY(-100%) rotate(45deg);
}
}
/* 结果卡片背景 */
.basic-info-card,
.bmi-card,
.weight-card,
.metabolism-card,
.body-fat-card,
.measurements-card,
.advice-card {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(15px);
border: 1px solid rgba(144, 238, 144, 0.2);
position: relative;
overflow: hidden;
}
/* 卡片悬停背景效果 */
.basic-info-card:hover,
.bmi-card:hover,
.weight-card:hover,
.metabolism-card:hover,
.body-fat-card:hover,
.measurements-card:hover,
.advice-card:hover {
background: rgba(255, 255, 255, 0.98);
border-color: rgba(76, 175, 80, 0.4);
}
/* 免责声明卡片背景 */
.disclaimer-card {
background: rgba(255, 243, 205, 0.95);
backdrop-filter: blur(15px);
border: 1px solid rgba(255, 234, 167, 0.5);
}
/* 错误区域背景 */
.error-content {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20px);
border: 1px solid rgba(220, 53, 69, 0.2);
}
/* 输入框背景 */
.form-input,
.form-select {
background: rgba(248, 255, 248, 0.9);
backdrop-filter: blur(10px);
}
.form-input:focus,
.form-select:focus {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(15px);
}
/* 信息项背景 */
.info-item {
background: rgba(248, 255, 248, 0.8);
backdrop-filter: blur(5px);
}
/* BMI分类背景 */
.bmi-category {
background: rgba(232, 245, 232, 0.9);
backdrop-filter: blur(10px);
}
/* 健康建议列表项背景 */
.health-tips li {
background: rgba(248, 255, 248, 0.8);
backdrop-filter: blur(5px);
}
/* 按钮背景增强 */
.submit-btn {
position: relative;
overflow: hidden;
}
.submit-btn::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
transition: left 0.5s;
}
.submit-btn:hover::before {
left: 100%;
}
/* 重置按钮背景 */
.reset-btn {
background: rgba(232, 245, 232, 0.9);
backdrop-filter: blur(10px);
}
.reset-btn:hover {
background: rgba(212, 237, 218, 0.95);
}
/* 响应式背景调整 */
@media (max-width: 767px) {
body::after {
background-size: 150px 75px;
animation-duration: 15s;
}
.form-section::before {
animation-duration: 2s;
}
}
@media (min-width: 768px) and (max-width: 1024px) {
body::after {
background-size: 180px 90px;
animation-duration: 18s;
}
}
@media (min-width: 1024px) {
body::after {
background-size: 220px 110px;
animation-duration: 25s;
}
.container {
background: rgba(255, 255, 255, 0.05);
}
}

View File

@@ -1,115 +1,115 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>身体健康分析</title>
<link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="background.css">
</head>
<body>
<div class="container">
<header class="header">
<h1 class="title">身体健康分析</h1>
<p class="subtitle">通过身高、体重、年龄、性别多维度分析身体健康状态</p>
</header>
<main class="main-content">
<div class="form-section">
<form id="healthForm" class="health-form">
<div class="form-group">
<label for="height" class="form-label">身高 (cm)</label>
<input type="number" id="height" name="height" class="form-input" placeholder="请输入身高" min="100" max="250" required>
</div>
<div class="form-group">
<label for="weight" class="form-label">体重 (kg)</label>
<input type="number" id="weight" name="weight" class="form-input" placeholder="请输入体重" min="30" max="200" required>
</div>
<div class="form-group">
<label for="age" class="form-label">年龄</label>
<input type="number" id="age" name="age" class="form-input" placeholder="请输入年龄" min="1" max="120" required>
</div>
<div class="form-group">
<label for="gender" class="form-label">性别</label>
<select id="gender" name="gender" class="form-select" required>
<option value="">请选择性别</option>
<option value="male">男性</option>
<option value="female">女性</option>
</select>
</div>
<button type="submit" class="submit-btn" id="analyzeBtn">
<span class="btn-text">开始分析</span>
<div class="loading-spinner" style="display: none;"></div>
</button>
</form>
</div>
<div class="result-section" id="resultSection" style="display: none;">
<div class="result-header">
<h2 class="result-title">分析结果</h2>
<button class="reset-btn" id="resetBtn">重新分析</button>
</div>
<div class="result-content">
<div class="basic-info-card">
<h3 class="card-title">基本信息</h3>
<div class="info-grid" id="basicInfo"></div>
</div>
<div class="bmi-card">
<h3 class="card-title">BMI 分析</h3>
<div class="bmi-content" id="bmiContent"></div>
</div>
<div class="weight-card">
<h3 class="card-title">体重评估</h3>
<div class="weight-content" id="weightContent"></div>
</div>
<div class="metabolism-card">
<h3 class="card-title">代谢分析</h3>
<div class="metabolism-content" id="metabolismContent"></div>
</div>
<div class="body-fat-card">
<h3 class="card-title">体脂分析</h3>
<div class="body-fat-content" id="bodyFatContent"></div>
</div>
<div class="measurements-card">
<h3 class="card-title">理想三围</h3>
<div class="measurements-content" id="measurementsContent"></div>
</div>
<div class="advice-card">
<h3 class="card-title">健康建议</h3>
<div class="advice-content" id="adviceContent"></div>
</div>
<div class="disclaimer-card">
<p class="disclaimer" id="disclaimer"></p>
</div>
</div>
</div>
<div class="error-section" id="errorSection" style="display: none;">
<div class="error-content">
<h3 class="error-title">分析失败</h3>
<p class="error-message" id="errorMessage"></p>
<button class="retry-btn" id="retryBtn">重试</button>
</div>
</div>
</main>
<footer class="footer">
<p class="footer-text">数据来源60s API</p>
</footer>
</div>
<script src="script.js"></script>
</body>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>身体健康分析</title>
<link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="background.css">
</head>
<body>
<div class="container">
<header class="header">
<h1 class="title">身体健康分析</h1>
<p class="subtitle">通过身高、体重、年龄、性别多维度分析身体健康状态</p>
</header>
<main class="main-content">
<div class="form-section">
<form id="healthForm" class="health-form">
<div class="form-group">
<label for="height" class="form-label">身高 (cm)</label>
<input type="number" id="height" name="height" class="form-input" placeholder="请输入身高" min="100" max="250" required>
</div>
<div class="form-group">
<label for="weight" class="form-label">体重 (kg)</label>
<input type="number" id="weight" name="weight" class="form-input" placeholder="请输入体重" min="30" max="200" required>
</div>
<div class="form-group">
<label for="age" class="form-label">年龄</label>
<input type="number" id="age" name="age" class="form-input" placeholder="请输入年龄" min="1" max="120" required>
</div>
<div class="form-group">
<label for="gender" class="form-label">性别</label>
<select id="gender" name="gender" class="form-select" required>
<option value="">请选择性别</option>
<option value="male">男性</option>
<option value="female">女性</option>
</select>
</div>
<button type="submit" class="submit-btn" id="analyzeBtn">
<span class="btn-text">开始分析</span>
<div class="loading-spinner" style="display: none;"></div>
</button>
</form>
</div>
<div class="result-section" id="resultSection" style="display: none;">
<div class="result-header">
<h2 class="result-title">分析结果</h2>
<button class="reset-btn" id="resetBtn">重新分析</button>
</div>
<div class="result-content">
<div class="basic-info-card">
<h3 class="card-title">基本信息</h3>
<div class="info-grid" id="basicInfo"></div>
</div>
<div class="bmi-card">
<h3 class="card-title">BMI 分析</h3>
<div class="bmi-content" id="bmiContent"></div>
</div>
<div class="weight-card">
<h3 class="card-title">体重评估</h3>
<div class="weight-content" id="weightContent"></div>
</div>
<div class="metabolism-card">
<h3 class="card-title">代谢分析</h3>
<div class="metabolism-content" id="metabolismContent"></div>
</div>
<div class="body-fat-card">
<h3 class="card-title">体脂分析</h3>
<div class="body-fat-content" id="bodyFatContent"></div>
</div>
<div class="measurements-card">
<h3 class="card-title">理想三围</h3>
<div class="measurements-content" id="measurementsContent"></div>
</div>
<div class="advice-card">
<h3 class="card-title">健康建议</h3>
<div class="advice-content" id="adviceContent"></div>
</div>
<div class="disclaimer-card">
<p class="disclaimer" id="disclaimer"></p>
</div>
</div>
</div>
<div class="error-section" id="errorSection" style="display: none;">
<div class="error-content">
<h3 class="error-title">分析失败</h3>
<p class="error-message" id="errorMessage"></p>
<button class="retry-btn" id="retryBtn">重试</button>
</div>
</div>
</main>
<footer class="footer">
<p class="footer-text">数据来源60s API</p>
</footer>
</div>
<script src="script.js"></script>
</body>
</html>

View File

@@ -1,93 +1,93 @@
{
"code": 200,
"message": "获取成功。数据来自官方/权威源头,以确保稳定与实时。开源地址 https://github.com/vikiboss/60s反馈群 595941841",
"data": {
"basic_info": {
"height": "176cm",
"height_desc": "身高",
"weight": "60kg",
"weight_desc": "体重",
"gender": "男性",
"gender_desc": "性别",
"age": "24岁",
"age_desc": "年龄"
},
"bmi": {
"value": 19.37,
"value_desc": "BMI 值",
"category": "正常体重",
"category_desc": "BMI 分类",
"evaluation": "体重正常,保持良好",
"evaluation_desc": "BMI 评价",
"risk": "健康风险较低",
"risk_desc": "健康风险"
},
"weight_assessment": {
"ideal_weight_range": "57.3-74.3kg",
"ideal_weight_range_desc": "理想体重范围",
"standard_weight": "71kg",
"standard_weight_desc": "标准体重",
"status": "体重正常",
"status_desc": "体重状态",
"adjustment": "保持当前体重",
"adjustment_desc": "调整建议"
},
"metabolism": {
"bmr": "1601 卡路里/天",
"bmr_desc": "基础代谢率",
"tdee": "2561 卡路里/天",
"tdee_desc": "每日总消耗",
"recommended_calories": "2561 卡路里/天",
"recommended_calories_desc": "推荐卡路里摄入",
"weight_loss_calories": "2061 卡路里/天",
"weight_loss_calories_desc": "减重卡路里",
"weight_gain_calories": "2861 卡路里/天",
"weight_gain_calories_desc": "增重卡路里"
},
"body_surface_area": {
"value": "1.74m²",
"value_desc": "体表面积",
"formula": "Du Bois 公式",
"formula_desc": "计算公式"
},
"body_fat": {
"percentage": "12.6%",
"percentage_desc": "体脂率",
"category": "正常",
"category_desc": "体脂分类",
"fat_weight": "7.6kg",
"fat_weight_desc": "脂肪重量",
"lean_weight": "52.4kg",
"lean_weight_desc": "瘦体重"
},
"health_advice": {
"daily_water_intake": "2000ml (约 8 杯水),运动时需额外补充 500-1000ml",
"daily_water_intake_desc": "每日饮水量",
"exercise_recommendation": "继续保持运动习惯,有氧运动和力量训练相结合效果更佳。年轻人可选择多样化的运动方式,建议每周运动 3-5 次",
"exercise_recommendation_desc": "运动建议",
"nutrition_advice": "保持均衡饮食,三大营养素合理搭配,定时定量进餐。年轻人新陈代谢较快,可适当增加能量摄入,男性可适当增加蛋白质摄入",
"nutrition_advice_desc": "营养建议",
"health_tips": [
"保持充足睡眠,成年人建议每天 7-9 小时",
"定期体检有助于早期发现健康问题",
"保持良好心态,适当释放压力",
"年轻人要注意作息规律,合理安排工作与休息",
"长时间用眼后适当休息,保护视力",
"培养兴趣爱好,保持积极的生活态度",
"多饮水,成年人每天 1500-2000ml 为宜"
],
"health_tips_desc": "健康提示"
},
"ideal_measurements": {
"chest": "84cm",
"chest_desc": "胸围",
"waist": "74cm",
"waist_desc": "腰围",
"hip": "83cm",
"hip_desc": "臀围",
"note": "男性理想三围参考标准",
"note_desc": "说明"
},
"disclaimer": "结果基于通用公式和统计数据,仅供参考,不能替代专业医疗建议。如有健康问题,请咨询医生。"
}
{
"code": 200,
"message": "获取成功。数据来自官方/权威源头,以确保稳定与实时。开源地址 https://github.com/vikiboss/60s反馈群 595941841",
"data": {
"basic_info": {
"height": "176cm",
"height_desc": "身高",
"weight": "60kg",
"weight_desc": "体重",
"gender": "男性",
"gender_desc": "性别",
"age": "24岁",
"age_desc": "年龄"
},
"bmi": {
"value": 19.37,
"value_desc": "BMI 值",
"category": "正常体重",
"category_desc": "BMI 分类",
"evaluation": "体重正常,保持良好",
"evaluation_desc": "BMI 评价",
"risk": "健康风险较低",
"risk_desc": "健康风险"
},
"weight_assessment": {
"ideal_weight_range": "57.3-74.3kg",
"ideal_weight_range_desc": "理想体重范围",
"standard_weight": "71kg",
"standard_weight_desc": "标准体重",
"status": "体重正常",
"status_desc": "体重状态",
"adjustment": "保持当前体重",
"adjustment_desc": "调整建议"
},
"metabolism": {
"bmr": "1601 卡路里/天",
"bmr_desc": "基础代谢率",
"tdee": "2561 卡路里/天",
"tdee_desc": "每日总消耗",
"recommended_calories": "2561 卡路里/天",
"recommended_calories_desc": "推荐卡路里摄入",
"weight_loss_calories": "2061 卡路里/天",
"weight_loss_calories_desc": "减重卡路里",
"weight_gain_calories": "2861 卡路里/天",
"weight_gain_calories_desc": "增重卡路里"
},
"body_surface_area": {
"value": "1.74m²",
"value_desc": "体表面积",
"formula": "Du Bois 公式",
"formula_desc": "计算公式"
},
"body_fat": {
"percentage": "12.6%",
"percentage_desc": "体脂率",
"category": "正常",
"category_desc": "体脂分类",
"fat_weight": "7.6kg",
"fat_weight_desc": "脂肪重量",
"lean_weight": "52.4kg",
"lean_weight_desc": "瘦体重"
},
"health_advice": {
"daily_water_intake": "2000ml (约 8 杯水),运动时需额外补充 500-1000ml",
"daily_water_intake_desc": "每日饮水量",
"exercise_recommendation": "继续保持运动习惯,有氧运动和力量训练相结合效果更佳。年轻人可选择多样化的运动方式,建议每周运动 3-5 次",
"exercise_recommendation_desc": "运动建议",
"nutrition_advice": "保持均衡饮食,三大营养素合理搭配,定时定量进餐。年轻人新陈代谢较快,可适当增加能量摄入,男性可适当增加蛋白质摄入",
"nutrition_advice_desc": "营养建议",
"health_tips": [
"保持充足睡眠,成年人建议每天 7-9 小时",
"定期体检有助于早期发现健康问题",
"保持良好心态,适当释放压力",
"年轻人要注意作息规律,合理安排工作与休息",
"长时间用眼后适当休息,保护视力",
"培养兴趣爱好,保持积极的生活态度",
"多饮水,成年人每天 1500-2000ml 为宜"
],
"health_tips_desc": "健康提示"
},
"ideal_measurements": {
"chest": "84cm",
"chest_desc": "胸围",
"waist": "74cm",
"waist_desc": "腰围",
"hip": "83cm",
"hip_desc": "臀围",
"note": "男性理想三围参考标准",
"note_desc": "说明"
},
"disclaimer": "结果基于通用公式和统计数据,仅供参考,不能替代专业医疗建议。如有健康问题,请咨询医生。"
}
}

View File

@@ -1,187 +1,187 @@
/* 背景样式文件 - 单独管理所有背景相关样式 */
/* 主体背景 */
body {
background: linear-gradient(135deg, #f0fff4 0%, #e6fffa 50%, #f0fff4 100%);
background-attachment: fixed;
position: relative;
}
/* 背景装饰元素 */
body::before {
content: '';
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image:
radial-gradient(circle at 20% 80%, rgba(104, 211, 145, 0.1) 0%, transparent 50%),
radial-gradient(circle at 80% 20%, rgba(72, 187, 120, 0.1) 0%, transparent 50%),
radial-gradient(circle at 40% 40%, rgba(56, 161, 105, 0.05) 0%, transparent 50%);
pointer-events: none;
z-index: -1;
}
/* 容器背景 */
.container {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 20px;
border: 1px solid rgba(255, 255, 255, 0.2);
}
/* 输入区域背景 */
.input-section {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20px);
border: 1px solid rgba(104, 211, 145, 0.2);
position: relative;
overflow: hidden;
}
.input-section::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 3px;
background: linear-gradient(90deg, #48bb78, #68d391, #9ae6b4);
}
/* 配色方案卡片背景 */
.palette {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(15px);
border: 1px solid rgba(104, 211, 145, 0.15);
position: relative;
overflow: hidden;
}
.palette::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 2px;
background: linear-gradient(90deg, transparent, #68d391, transparent);
opacity: 0;
transition: opacity 0.3s ease;
}
.palette:hover::before {
opacity: 1;
}
/* 颜色信息背景 */
.color-info {
background: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(10px);
border: 1px solid rgba(104, 211, 145, 0.2);
}
/* 颜色项背景 */
.color-item {
background: rgba(255, 255, 255, 0.8);
backdrop-filter: blur(5px);
border: 1px solid rgba(104, 211, 145, 0.15);
position: relative;
}
.color-item::after {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(45deg, transparent 48%, rgba(104, 211, 145, 0.05) 50%, transparent 52%);
opacity: 0;
transition: opacity 0.3s ease;
pointer-events: none;
}
.color-item:hover::after {
opacity: 1;
}
/* 颜色详情背景 */
.color-detail {
background: rgba(104, 211, 145, 0.08);
border: 1px solid rgba(104, 211, 145, 0.1);
position: relative;
}
.color-detail::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(135deg, rgba(255, 255, 255, 0.1) 0%, transparent 100%);
pointer-events: none;
}
/* 按钮背景 */
.generate-btn {
background: linear-gradient(135deg, #48bb78 0%, #68d391 50%, #9ae6b4 100%);
position: relative;
overflow: hidden;
}
.generate-btn::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
transition: left 0.5s ease;
}
.generate-btn:hover::before {
left: 100%;
}
/* 加载动画背景 */
.loading {
background: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(10px);
border-radius: 12px;
border: 1px solid rgba(104, 211, 145, 0.2);
}
/* 响应式背景调整 */
@media (max-width: 768px) {
body {
background: linear-gradient(180deg, #f0fff4 0%, #e6fffa 100%);
}
.container {
background: rgba(255, 255, 255, 0.05);
backdrop-filter: blur(5px);
}
.input-section,
.palette,
.color-info {
backdrop-filter: blur(10px);
}
}
@media (max-width: 480px) {
body::before {
background-image:
radial-gradient(circle at 50% 50%, rgba(104, 211, 145, 0.08) 0%, transparent 70%);
}
.container {
background: transparent;
backdrop-filter: none;
border: none;
}
/* 背景样式文件 - 单独管理所有背景相关样式 */
/* 主体背景 */
body {
background: linear-gradient(135deg, #f0fff4 0%, #e6fffa 50%, #f0fff4 100%);
background-attachment: fixed;
position: relative;
}
/* 背景装饰元素 */
body::before {
content: '';
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image:
radial-gradient(circle at 20% 80%, rgba(104, 211, 145, 0.1) 0%, transparent 50%),
radial-gradient(circle at 80% 20%, rgba(72, 187, 120, 0.1) 0%, transparent 50%),
radial-gradient(circle at 40% 40%, rgba(56, 161, 105, 0.05) 0%, transparent 50%);
pointer-events: none;
z-index: -1;
}
/* 容器背景 */
.container {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 20px;
border: 1px solid rgba(255, 255, 255, 0.2);
}
/* 输入区域背景 */
.input-section {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20px);
border: 1px solid rgba(104, 211, 145, 0.2);
position: relative;
overflow: hidden;
}
.input-section::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 3px;
background: linear-gradient(90deg, #48bb78, #68d391, #9ae6b4);
}
/* 配色方案卡片背景 */
.palette {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(15px);
border: 1px solid rgba(104, 211, 145, 0.15);
position: relative;
overflow: hidden;
}
.palette::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 2px;
background: linear-gradient(90deg, transparent, #68d391, transparent);
opacity: 0;
transition: opacity 0.3s ease;
}
.palette:hover::before {
opacity: 1;
}
/* 颜色信息背景 */
.color-info {
background: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(10px);
border: 1px solid rgba(104, 211, 145, 0.2);
}
/* 颜色项背景 */
.color-item {
background: rgba(255, 255, 255, 0.8);
backdrop-filter: blur(5px);
border: 1px solid rgba(104, 211, 145, 0.15);
position: relative;
}
.color-item::after {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(45deg, transparent 48%, rgba(104, 211, 145, 0.05) 50%, transparent 52%);
opacity: 0;
transition: opacity 0.3s ease;
pointer-events: none;
}
.color-item:hover::after {
opacity: 1;
}
/* 颜色详情背景 */
.color-detail {
background: rgba(104, 211, 145, 0.08);
border: 1px solid rgba(104, 211, 145, 0.1);
position: relative;
}
.color-detail::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(135deg, rgba(255, 255, 255, 0.1) 0%, transparent 100%);
pointer-events: none;
}
/* 按钮背景 */
.generate-btn {
background: linear-gradient(135deg, #48bb78 0%, #68d391 50%, #9ae6b4 100%);
position: relative;
overflow: hidden;
}
.generate-btn::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
transition: left 0.5s ease;
}
.generate-btn:hover::before {
left: 100%;
}
/* 加载动画背景 */
.loading {
background: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(10px);
border-radius: 12px;
border: 1px solid rgba(104, 211, 145, 0.2);
}
/* 响应式背景调整 */
@media (max-width: 768px) {
body {
background: linear-gradient(180deg, #f0fff4 0%, #e6fffa 100%);
}
.container {
background: rgba(255, 255, 255, 0.05);
backdrop-filter: blur(5px);
}
.input-section,
.palette,
.color-info {
backdrop-filter: blur(10px);
}
}
@media (max-width: 480px) {
body::before {
background-image:
radial-gradient(circle at 50% 50%, rgba(104, 211, 145, 0.08) 0%, transparent 70%);
}
.container {
background: transparent;
backdrop-filter: none;
border: none;
}
}

View File

@@ -1,64 +1,64 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>配色方案生成器</title>
<link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="background.css">
</head>
<body>
<div class="container">
<header class="header">
<h1>配色方案生成器</h1>
<p class="subtitle">输入颜色值,获取专业的配色方案</p>
</header>
<main class="main-content">
<section class="input-section">
<div class="color-input-group">
<label for="colorInput">颜色值</label>
<div class="input-wrapper">
<input type="text" id="colorInput" placeholder="#33AAFF" value="#DE4F99">
<input type="color" id="colorPicker" value="#DE4F99">
</div>
</div>
<div class="format-select">
<label for="formatSelect">输出格式</label>
<select id="formatSelect">
<option value="json">JSON</option>
<option value="text">文本</option>
<option value="html">HTML</option>
</select>
</div>
<button id="generateBtn" class="generate-btn">生成配色方案</button>
</section>
<section class="result-section" id="resultSection">
<div class="loading" id="loading" style="display: none;">
<div class="spinner"></div>
<p>正在生成配色方案...</p>
</div>
<div class="color-info" id="colorInfo" style="display: none;">
<h3>输入颜色信息</h3>
<div class="color-preview" id="colorPreview"></div>
<div class="color-details" id="colorDetails"></div>
</div>
<div class="palettes-container" id="palettesContainer">
<!-- 配色方案将在这里动态生成 -->
</div>
</section>
</main>
<footer class="footer">
<p>基于色彩理论的专业配色方案生成</p>
</footer>
</div>
<script src="script.js"></script>
</body>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>配色方案生成器</title>
<link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="background.css">
</head>
<body>
<div class="container">
<header class="header">
<h1>配色方案生成器</h1>
<p class="subtitle">输入颜色值,获取专业的配色方案</p>
</header>
<main class="main-content">
<section class="input-section">
<div class="color-input-group">
<label for="colorInput">颜色值</label>
<div class="input-wrapper">
<input type="text" id="colorInput" placeholder="#33AAFF" value="#DE4F99">
<input type="color" id="colorPicker" value="#DE4F99">
</div>
</div>
<div class="format-select">
<label for="formatSelect">输出格式</label>
<select id="formatSelect">
<option value="json">JSON</option>
<option value="text">文本</option>
<option value="html">HTML</option>
</select>
</div>
<button id="generateBtn" class="generate-btn">生成配色方案</button>
</section>
<section class="result-section" id="resultSection">
<div class="loading" id="loading" style="display: none;">
<div class="spinner"></div>
<p>正在生成配色方案...</p>
</div>
<div class="color-info" id="colorInfo" style="display: none;">
<h3>输入颜色信息</h3>
<div class="color-preview" id="colorPreview"></div>
<div class="color-details" id="colorDetails"></div>
</div>
<div class="palettes-container" id="palettesContainer">
<!-- 配色方案将在这里动态生成 -->
</div>
</section>
</main>
<footer class="footer">
<p>基于色彩理论的专业配色方案生成</p>
</footer>
</div>
<script src="script.js"></script>
</body>
</html>

View File

@@ -1,315 +1,315 @@
// 配色方案生成器 JavaScript
class ColorPaletteGenerator {
constructor() {
this.apiUrl = 'https://60s.api.shumengya.top/v2/color/palette';
this.init();
}
init() {
this.bindEvents();
this.loadDefaultPalette();
}
bindEvents() {
const colorInput = document.getElementById('colorInput');
const colorPicker = document.getElementById('colorPicker');
const generateBtn = document.getElementById('generateBtn');
const formatSelect = document.getElementById('formatSelect');
// 颜色输入框事件
colorInput.addEventListener('input', (e) => {
const color = e.target.value;
if (this.isValidColor(color)) {
colorPicker.value = color;
}
});
// 颜色选择器事件
colorPicker.addEventListener('change', (e) => {
colorInput.value = e.target.value;
});
// 生成按钮事件
generateBtn.addEventListener('click', () => {
this.generatePalette();
});
// 回车键生成
colorInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
this.generatePalette();
}
});
// 格式选择事件
formatSelect.addEventListener('change', () => {
const currentColor = colorInput.value;
if (currentColor && this.isValidColor(currentColor)) {
this.generatePalette();
}
});
}
// 验证颜色格式
isValidColor(color) {
const hexRegex = /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/;
return hexRegex.test(color);
}
// 显示加载状态
showLoading() {
const loading = document.getElementById('loading');
const colorInfo = document.getElementById('colorInfo');
const palettesContainer = document.getElementById('palettesContainer');
loading.style.display = 'block';
colorInfo.style.display = 'none';
palettesContainer.innerHTML = '';
}
// 隐藏加载状态
hideLoading() {
const loading = document.getElementById('loading');
loading.style.display = 'none';
}
// 生成配色方案
async generatePalette() {
const colorInput = document.getElementById('colorInput');
const formatSelect = document.getElementById('formatSelect');
const color = colorInput.value.trim();
const format = formatSelect.value;
if (!color) {
this.showError('请输入颜色值');
return;
}
if (!this.isValidColor(color)) {
this.showError('请输入有效的十六进制颜色值(如:#33AAFF');
return;
}
this.showLoading();
try {
const url = new URL(this.apiUrl);
url.searchParams.append('color', color);
url.searchParams.append('encoding', format);
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
if (data.code === 200) {
this.displayResults(data.data);
} else {
throw new Error(data.message || '获取配色方案失败');
}
} catch (error) {
console.error('Error:', error);
this.showError('获取配色方案失败,请检查网络连接或稍后重试');
} finally {
this.hideLoading();
}
}
// 显示错误信息
showError(message) {
const palettesContainer = document.getElementById('palettesContainer');
palettesContainer.innerHTML = `
<div class="error-message" style="
background: rgba(254, 226, 226, 0.9);
border: 1px solid #feb2b2;
color: #c53030;
padding: 20px;
border-radius: 8px;
text-align: center;
font-weight: 500;
">
<p>❌ ${message}</p>
</div>
`;
}
// 显示结果
displayResults(data) {
this.displayColorInfo(data.input);
this.displayPalettes(data.palettes);
}
// 显示颜色信息
displayColorInfo(inputData) {
const colorInfo = document.getElementById('colorInfo');
const colorPreview = document.getElementById('colorPreview');
const colorDetails = document.getElementById('colorDetails');
colorPreview.style.backgroundColor = inputData.hex;
colorDetails.innerHTML = `
<div class="color-detail">
<strong>HEX</strong>
<span>${inputData.hex}</span>
</div>
<div class="color-detail">
<strong>RGB</strong>
<span>rgb(${inputData.rgb.r}, ${inputData.rgb.g}, ${inputData.rgb.b})</span>
</div>
<div class="color-detail">
<strong>HSL</strong>
<span>hsl(${inputData.hsl.h}°, ${inputData.hsl.s}%, ${inputData.hsl.l}%)</span>
</div>
<div class="color-detail">
<strong>色系</strong>
<span>${inputData.name}</span>
</div>
`;
colorInfo.style.display = 'block';
}
// 显示配色方案
displayPalettes(palettes) {
const palettesContainer = document.getElementById('palettesContainer');
palettesContainer.innerHTML = palettes.map(palette => `
<div class="palette">
<div class="palette-header">
<h3 class="palette-name">${palette.name}</h3>
<p class="palette-description">${palette.description}</p>
</div>
<div class="colors-grid">
${palette.colors.map(color => `
<div class="color-item">
<div class="color-swatch"
style="background-color: ${color.hex}"
onclick="copyToClipboard('${color.hex}')"
title="点击复制 ${color.hex}">
</div>
<div class="color-name">${color.name}</div>
<div class="color-hex">${color.hex}</div>
<div class="color-role">${color.role}${color.theory}</div>
</div>
`).join('')}
</div>
</div>
`).join('');
}
// 加载默认配色方案
async loadDefaultPalette() {
const colorInput = document.getElementById('colorInput');
const defaultColor = colorInput.value;
if (defaultColor && this.isValidColor(defaultColor)) {
await this.generatePalette();
}
}
}
// 复制到剪贴板功能
function copyToClipboard(text) {
if (navigator.clipboard && window.isSecureContext) {
navigator.clipboard.writeText(text).then(() => {
showToast(`已复制 ${text} 到剪贴板`);
}).catch(err => {
console.error('复制失败:', err);
fallbackCopyTextToClipboard(text);
});
} else {
fallbackCopyTextToClipboard(text);
}
}
// 备用复制方法
function fallbackCopyTextToClipboard(text) {
const textArea = document.createElement('textarea');
textArea.value = text;
textArea.style.position = 'fixed';
textArea.style.left = '-999999px';
textArea.style.top = '-999999px';
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
try {
document.execCommand('copy');
showToast(`已复制 ${text} 到剪贴板`);
} catch (err) {
console.error('复制失败:', err);
showToast('复制失败,请手动复制');
}
document.body.removeChild(textArea);
}
// 显示提示信息
function showToast(message) {
// 移除已存在的toast
const existingToast = document.querySelector('.toast');
if (existingToast) {
existingToast.remove();
}
const toast = document.createElement('div');
toast.className = 'toast';
toast.textContent = message;
toast.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: rgba(45, 90, 39, 0.95);
color: white;
padding: 12px 20px;
border-radius: 8px;
font-size: 14px;
font-weight: 500;
z-index: 10000;
box-shadow: 0 4px 12px rgba(45, 90, 39, 0.3);
transform: translateX(100%);
transition: transform 0.3s ease;
backdrop-filter: blur(10px);
`;
document.body.appendChild(toast);
// 动画显示
setTimeout(() => {
toast.style.transform = 'translateX(0)';
}, 100);
// 3秒后隐藏
setTimeout(() => {
toast.style.transform = 'translateX(100%)';
setTimeout(() => {
if (toast.parentNode) {
toast.parentNode.removeChild(toast);
}
}, 300);
}, 3000);
}
// 页面加载完成后初始化
document.addEventListener('DOMContentLoaded', () => {
new ColorPaletteGenerator();
});
// 添加移动端优化
if ('ontouchstart' in window) {
// 移动端触摸优化
document.addEventListener('touchstart', function() {}, {passive: true});
// 防止双击缩放
let lastTouchEnd = 0;
document.addEventListener('touchend', function (event) {
const now = (new Date()).getTime();
if (now - lastTouchEnd <= 300) {
event.preventDefault();
}
lastTouchEnd = now;
}, false);
// 配色方案生成器 JavaScript
class ColorPaletteGenerator {
constructor() {
this.apiUrl = 'https://60s.api.shumengya.top/v2/color/palette';
this.init();
}
init() {
this.bindEvents();
this.loadDefaultPalette();
}
bindEvents() {
const colorInput = document.getElementById('colorInput');
const colorPicker = document.getElementById('colorPicker');
const generateBtn = document.getElementById('generateBtn');
const formatSelect = document.getElementById('formatSelect');
// 颜色输入框事件
colorInput.addEventListener('input', (e) => {
const color = e.target.value;
if (this.isValidColor(color)) {
colorPicker.value = color;
}
});
// 颜色选择器事件
colorPicker.addEventListener('change', (e) => {
colorInput.value = e.target.value;
});
// 生成按钮事件
generateBtn.addEventListener('click', () => {
this.generatePalette();
});
// 回车键生成
colorInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
this.generatePalette();
}
});
// 格式选择事件
formatSelect.addEventListener('change', () => {
const currentColor = colorInput.value;
if (currentColor && this.isValidColor(currentColor)) {
this.generatePalette();
}
});
}
// 验证颜色格式
isValidColor(color) {
const hexRegex = /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/;
return hexRegex.test(color);
}
// 显示加载状态
showLoading() {
const loading = document.getElementById('loading');
const colorInfo = document.getElementById('colorInfo');
const palettesContainer = document.getElementById('palettesContainer');
loading.style.display = 'block';
colorInfo.style.display = 'none';
palettesContainer.innerHTML = '';
}
// 隐藏加载状态
hideLoading() {
const loading = document.getElementById('loading');
loading.style.display = 'none';
}
// 生成配色方案
async generatePalette() {
const colorInput = document.getElementById('colorInput');
const formatSelect = document.getElementById('formatSelect');
const color = colorInput.value.trim();
const format = formatSelect.value;
if (!color) {
this.showError('请输入颜色值');
return;
}
if (!this.isValidColor(color)) {
this.showError('请输入有效的十六进制颜色值(如:#33AAFF');
return;
}
this.showLoading();
try {
const url = new URL(this.apiUrl);
url.searchParams.append('color', color);
url.searchParams.append('encoding', format);
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
if (data.code === 200) {
this.displayResults(data.data);
} else {
throw new Error(data.message || '获取配色方案失败');
}
} catch (error) {
console.error('Error:', error);
this.showError('获取配色方案失败,请检查网络连接或稍后重试');
} finally {
this.hideLoading();
}
}
// 显示错误信息
showError(message) {
const palettesContainer = document.getElementById('palettesContainer');
palettesContainer.innerHTML = `
<div class="error-message" style="
background: rgba(254, 226, 226, 0.9);
border: 1px solid #feb2b2;
color: #c53030;
padding: 20px;
border-radius: 8px;
text-align: center;
font-weight: 500;
">
<p>❌ ${message}</p>
</div>
`;
}
// 显示结果
displayResults(data) {
this.displayColorInfo(data.input);
this.displayPalettes(data.palettes);
}
// 显示颜色信息
displayColorInfo(inputData) {
const colorInfo = document.getElementById('colorInfo');
const colorPreview = document.getElementById('colorPreview');
const colorDetails = document.getElementById('colorDetails');
colorPreview.style.backgroundColor = inputData.hex;
colorDetails.innerHTML = `
<div class="color-detail">
<strong>HEX</strong>
<span>${inputData.hex}</span>
</div>
<div class="color-detail">
<strong>RGB</strong>
<span>rgb(${inputData.rgb.r}, ${inputData.rgb.g}, ${inputData.rgb.b})</span>
</div>
<div class="color-detail">
<strong>HSL</strong>
<span>hsl(${inputData.hsl.h}°, ${inputData.hsl.s}%, ${inputData.hsl.l}%)</span>
</div>
<div class="color-detail">
<strong>色系</strong>
<span>${inputData.name}</span>
</div>
`;
colorInfo.style.display = 'block';
}
// 显示配色方案
displayPalettes(palettes) {
const palettesContainer = document.getElementById('palettesContainer');
palettesContainer.innerHTML = palettes.map(palette => `
<div class="palette">
<div class="palette-header">
<h3 class="palette-name">${palette.name}</h3>
<p class="palette-description">${palette.description}</p>
</div>
<div class="colors-grid">
${palette.colors.map(color => `
<div class="color-item">
<div class="color-swatch"
style="background-color: ${color.hex}"
onclick="copyToClipboard('${color.hex}')"
title="点击复制 ${color.hex}">
</div>
<div class="color-name">${color.name}</div>
<div class="color-hex">${color.hex}</div>
<div class="color-role">${color.role}${color.theory}</div>
</div>
`).join('')}
</div>
</div>
`).join('');
}
// 加载默认配色方案
async loadDefaultPalette() {
const colorInput = document.getElementById('colorInput');
const defaultColor = colorInput.value;
if (defaultColor && this.isValidColor(defaultColor)) {
await this.generatePalette();
}
}
}
// 复制到剪贴板功能
function copyToClipboard(text) {
if (navigator.clipboard && window.isSecureContext) {
navigator.clipboard.writeText(text).then(() => {
showToast(`已复制 ${text} 到剪贴板`);
}).catch(err => {
console.error('复制失败:', err);
fallbackCopyTextToClipboard(text);
});
} else {
fallbackCopyTextToClipboard(text);
}
}
// 备用复制方法
function fallbackCopyTextToClipboard(text) {
const textArea = document.createElement('textarea');
textArea.value = text;
textArea.style.position = 'fixed';
textArea.style.left = '-999999px';
textArea.style.top = '-999999px';
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
try {
document.execCommand('copy');
showToast(`已复制 ${text} 到剪贴板`);
} catch (err) {
console.error('复制失败:', err);
showToast('复制失败,请手动复制');
}
document.body.removeChild(textArea);
}
// 显示提示信息
function showToast(message) {
// 移除已存在的toast
const existingToast = document.querySelector('.toast');
if (existingToast) {
existingToast.remove();
}
const toast = document.createElement('div');
toast.className = 'toast';
toast.textContent = message;
toast.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: rgba(45, 90, 39, 0.95);
color: white;
padding: 12px 20px;
border-radius: 8px;
font-size: 14px;
font-weight: 500;
z-index: 10000;
box-shadow: 0 4px 12px rgba(45, 90, 39, 0.3);
transform: translateX(100%);
transition: transform 0.3s ease;
backdrop-filter: blur(10px);
`;
document.body.appendChild(toast);
// 动画显示
setTimeout(() => {
toast.style.transform = 'translateX(0)';
}, 100);
// 3秒后隐藏
setTimeout(() => {
toast.style.transform = 'translateX(100%)';
setTimeout(() => {
if (toast.parentNode) {
toast.parentNode.removeChild(toast);
}
}, 300);
}, 3000);
}
// 页面加载完成后初始化
document.addEventListener('DOMContentLoaded', () => {
new ColorPaletteGenerator();
});
// 添加移动端优化
if ('ontouchstart' in window) {
// 移动端触摸优化
document.addEventListener('touchstart', function() {}, {passive: true});
// 防止双击缩放
let lastTouchEnd = 0;
document.addEventListener('touchend', function (event) {
const now = (new Date()).getTime();
if (now - lastTouchEnd <= 300) {
event.preventDefault();
}
lastTouchEnd = now;
}, false);
}

View File

@@ -1,422 +1,422 @@
/* 基础样式重置 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
line-height: 1.6;
color: #2d3748;
min-height: 100vh;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
min-height: 100vh;
display: flex;
flex-direction: column;
}
/* 头部样式 */
.header {
text-align: center;
margin-bottom: 40px;
padding: 30px 0;
}
.header h1 {
font-size: 2.5rem;
color: #2d5a27;
margin-bottom: 10px;
font-weight: 700;
}
.subtitle {
font-size: 1.1rem;
color: #68d391;
font-weight: 400;
}
/* 主内容区域 */
.main-content {
flex: 1;
display: flex;
flex-direction: column;
gap: 30px;
}
/* 输入区域 */
.input-section {
background: rgba(255, 255, 255, 0.9);
padding: 30px;
border-radius: 16px;
box-shadow: 0 4px 20px rgba(45, 90, 39, 0.1);
border: 1px solid rgba(104, 211, 145, 0.2);
}
.color-input-group {
margin-bottom: 20px;
}
.color-input-group label,
.format-select label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: #2d5a27;
font-size: 0.95rem;
}
.input-wrapper {
display: flex;
gap: 10px;
align-items: center;
}
#colorInput {
flex: 1;
padding: 12px 16px;
border: 2px solid #e2e8f0;
border-radius: 8px;
font-size: 1rem;
transition: all 0.3s ease;
background: white;
}
#colorInput:focus {
outline: none;
border-color: #68d391;
box-shadow: 0 0 0 3px rgba(104, 211, 145, 0.1);
}
#colorPicker {
width: 50px;
height: 44px;
border: 2px solid #e2e8f0;
border-radius: 8px;
cursor: pointer;
background: none;
}
.format-select {
margin-bottom: 25px;
}
#formatSelect {
width: 100%;
padding: 12px 16px;
border: 2px solid #e2e8f0;
border-radius: 8px;
font-size: 1rem;
background: white;
cursor: pointer;
transition: all 0.3s ease;
}
#formatSelect:focus {
outline: none;
border-color: #68d391;
box-shadow: 0 0 0 3px rgba(104, 211, 145, 0.1);
}
.generate-btn {
width: 100%;
padding: 14px 24px;
background: linear-gradient(135deg, #48bb78, #68d391);
color: white;
border: none;
border-radius: 8px;
font-size: 1.1rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 4px 12px rgba(72, 187, 120, 0.3);
}
.generate-btn:hover {
background: linear-gradient(135deg, #38a169, #48bb78);
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(72, 187, 120, 0.4);
}
.generate-btn:active {
transform: translateY(0);
}
/* 结果区域 */
.result-section {
min-height: 200px;
}
.loading {
text-align: center;
padding: 40px;
}
.spinner {
width: 40px;
height: 40px;
border: 4px solid #e2e8f0;
border-top: 4px solid #68d391;
border-radius: 50%;
animation: spin 1s linear infinite;
margin: 0 auto 20px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.loading p {
color: #68d391;
font-weight: 500;
}
/* 颜色信息 */
.color-info {
background: rgba(255, 255, 255, 0.9);
padding: 25px;
border-radius: 12px;
margin-bottom: 25px;
box-shadow: 0 2px 10px rgba(45, 90, 39, 0.1);
border: 1px solid rgba(104, 211, 145, 0.2);
}
.color-info h3 {
color: #2d5a27;
margin-bottom: 15px;
font-size: 1.3rem;
}
.color-preview {
width: 100%;
height: 60px;
border-radius: 8px;
margin-bottom: 15px;
border: 2px solid rgba(104, 211, 145, 0.3);
}
.color-details {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
gap: 15px;
}
.color-detail {
text-align: center;
padding: 10px;
background: rgba(104, 211, 145, 0.1);
border-radius: 6px;
}
.color-detail strong {
display: block;
color: #2d5a27;
font-size: 0.9rem;
margin-bottom: 5px;
}
.color-detail span {
color: #4a5568;
font-size: 0.95rem;
}
/* 配色方案容器 */
.palettes-container {
display: grid;
gap: 25px;
}
.palette {
background: rgba(255, 255, 255, 0.9);
border-radius: 12px;
padding: 25px;
box-shadow: 0 4px 15px rgba(45, 90, 39, 0.1);
border: 1px solid rgba(104, 211, 145, 0.2);
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.palette:hover {
transform: translateY(-3px);
box-shadow: 0 8px 25px rgba(45, 90, 39, 0.15);
}
.palette-header {
margin-bottom: 20px;
}
.palette-name {
font-size: 1.4rem;
color: #2d5a27;
margin-bottom: 8px;
font-weight: 600;
}
.palette-description {
color: #68d391;
font-size: 0.95rem;
line-height: 1.5;
}
.colors-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
}
.color-item {
background: white;
border-radius: 8px;
padding: 15px;
border: 1px solid rgba(104, 211, 145, 0.2);
transition: all 0.3s ease;
}
.color-item:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(45, 90, 39, 0.1);
}
.color-swatch {
width: 100%;
height: 50px;
border-radius: 6px;
margin-bottom: 10px;
border: 1px solid rgba(0, 0, 0, 0.1);
cursor: pointer;
position: relative;
overflow: hidden;
}
.color-swatch::after {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(45deg, transparent 45%, rgba(255,255,255,0.1) 50%, transparent 55%);
opacity: 0;
transition: opacity 0.3s ease;
}
.color-swatch:hover::after {
opacity: 1;
}
.color-name {
font-weight: 600;
color: #2d5a27;
margin-bottom: 5px;
font-size: 0.9rem;
}
.color-hex {
font-family: 'Courier New', monospace;
color: #4a5568;
font-size: 0.85rem;
margin-bottom: 3px;
}
.color-role {
font-size: 0.8rem;
color: #68d391;
font-style: italic;
}
/* 底部 */
.footer {
text-align: center;
padding: 30px 0;
margin-top: 40px;
color: #68d391;
font-size: 0.9rem;
}
/* 平板端适配 */
@media (max-width: 1024px) {
.container {
padding: 15px;
}
.header h1 {
font-size: 2.2rem;
}
.colors-grid {
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
}
}
/* 手机端适配 */
@media (max-width: 768px) {
.container {
padding: 10px;
}
.header {
margin-bottom: 25px;
padding: 20px 0;
}
.header h1 {
font-size: 1.8rem;
}
.subtitle {
font-size: 1rem;
}
.input-section {
padding: 20px;
}
.input-wrapper {
flex-direction: column;
align-items: stretch;
}
#colorPicker {
width: 100%;
height: 44px;
}
.colors-grid {
grid-template-columns: 1fr;
}
.color-details {
grid-template-columns: repeat(2, 1fr);
}
.palette {
padding: 20px;
}
.palette-name {
font-size: 1.2rem;
}
}
@media (max-width: 480px) {
.header h1 {
font-size: 1.6rem;
}
.input-section {
padding: 15px;
}
.palette {
padding: 15px;
}
.color-details {
grid-template-columns: 1fr;
}
/* 基础样式重置 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
line-height: 1.6;
color: #2d3748;
min-height: 100vh;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
min-height: 100vh;
display: flex;
flex-direction: column;
}
/* 头部样式 */
.header {
text-align: center;
margin-bottom: 40px;
padding: 30px 0;
}
.header h1 {
font-size: 2.5rem;
color: #2d5a27;
margin-bottom: 10px;
font-weight: 700;
}
.subtitle {
font-size: 1.1rem;
color: #68d391;
font-weight: 400;
}
/* 主内容区域 */
.main-content {
flex: 1;
display: flex;
flex-direction: column;
gap: 30px;
}
/* 输入区域 */
.input-section {
background: rgba(255, 255, 255, 0.9);
padding: 30px;
border-radius: 16px;
box-shadow: 0 4px 20px rgba(45, 90, 39, 0.1);
border: 1px solid rgba(104, 211, 145, 0.2);
}
.color-input-group {
margin-bottom: 20px;
}
.color-input-group label,
.format-select label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: #2d5a27;
font-size: 0.95rem;
}
.input-wrapper {
display: flex;
gap: 10px;
align-items: center;
}
#colorInput {
flex: 1;
padding: 12px 16px;
border: 2px solid #e2e8f0;
border-radius: 8px;
font-size: 1rem;
transition: all 0.3s ease;
background: white;
}
#colorInput:focus {
outline: none;
border-color: #68d391;
box-shadow: 0 0 0 3px rgba(104, 211, 145, 0.1);
}
#colorPicker {
width: 50px;
height: 44px;
border: 2px solid #e2e8f0;
border-radius: 8px;
cursor: pointer;
background: none;
}
.format-select {
margin-bottom: 25px;
}
#formatSelect {
width: 100%;
padding: 12px 16px;
border: 2px solid #e2e8f0;
border-radius: 8px;
font-size: 1rem;
background: white;
cursor: pointer;
transition: all 0.3s ease;
}
#formatSelect:focus {
outline: none;
border-color: #68d391;
box-shadow: 0 0 0 3px rgba(104, 211, 145, 0.1);
}
.generate-btn {
width: 100%;
padding: 14px 24px;
background: linear-gradient(135deg, #48bb78, #68d391);
color: white;
border: none;
border-radius: 8px;
font-size: 1.1rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 4px 12px rgba(72, 187, 120, 0.3);
}
.generate-btn:hover {
background: linear-gradient(135deg, #38a169, #48bb78);
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(72, 187, 120, 0.4);
}
.generate-btn:active {
transform: translateY(0);
}
/* 结果区域 */
.result-section {
min-height: 200px;
}
.loading {
text-align: center;
padding: 40px;
}
.spinner {
width: 40px;
height: 40px;
border: 4px solid #e2e8f0;
border-top: 4px solid #68d391;
border-radius: 50%;
animation: spin 1s linear infinite;
margin: 0 auto 20px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.loading p {
color: #68d391;
font-weight: 500;
}
/* 颜色信息 */
.color-info {
background: rgba(255, 255, 255, 0.9);
padding: 25px;
border-radius: 12px;
margin-bottom: 25px;
box-shadow: 0 2px 10px rgba(45, 90, 39, 0.1);
border: 1px solid rgba(104, 211, 145, 0.2);
}
.color-info h3 {
color: #2d5a27;
margin-bottom: 15px;
font-size: 1.3rem;
}
.color-preview {
width: 100%;
height: 60px;
border-radius: 8px;
margin-bottom: 15px;
border: 2px solid rgba(104, 211, 145, 0.3);
}
.color-details {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
gap: 15px;
}
.color-detail {
text-align: center;
padding: 10px;
background: rgba(104, 211, 145, 0.1);
border-radius: 6px;
}
.color-detail strong {
display: block;
color: #2d5a27;
font-size: 0.9rem;
margin-bottom: 5px;
}
.color-detail span {
color: #4a5568;
font-size: 0.95rem;
}
/* 配色方案容器 */
.palettes-container {
display: grid;
gap: 25px;
}
.palette {
background: rgba(255, 255, 255, 0.9);
border-radius: 12px;
padding: 25px;
box-shadow: 0 4px 15px rgba(45, 90, 39, 0.1);
border: 1px solid rgba(104, 211, 145, 0.2);
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.palette:hover {
transform: translateY(-3px);
box-shadow: 0 8px 25px rgba(45, 90, 39, 0.15);
}
.palette-header {
margin-bottom: 20px;
}
.palette-name {
font-size: 1.4rem;
color: #2d5a27;
margin-bottom: 8px;
font-weight: 600;
}
.palette-description {
color: #68d391;
font-size: 0.95rem;
line-height: 1.5;
}
.colors-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
}
.color-item {
background: white;
border-radius: 8px;
padding: 15px;
border: 1px solid rgba(104, 211, 145, 0.2);
transition: all 0.3s ease;
}
.color-item:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(45, 90, 39, 0.1);
}
.color-swatch {
width: 100%;
height: 50px;
border-radius: 6px;
margin-bottom: 10px;
border: 1px solid rgba(0, 0, 0, 0.1);
cursor: pointer;
position: relative;
overflow: hidden;
}
.color-swatch::after {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(45deg, transparent 45%, rgba(255,255,255,0.1) 50%, transparent 55%);
opacity: 0;
transition: opacity 0.3s ease;
}
.color-swatch:hover::after {
opacity: 1;
}
.color-name {
font-weight: 600;
color: #2d5a27;
margin-bottom: 5px;
font-size: 0.9rem;
}
.color-hex {
font-family: 'Courier New', monospace;
color: #4a5568;
font-size: 0.85rem;
margin-bottom: 3px;
}
.color-role {
font-size: 0.8rem;
color: #68d391;
font-style: italic;
}
/* 底部 */
.footer {
text-align: center;
padding: 30px 0;
margin-top: 40px;
color: #68d391;
font-size: 0.9rem;
}
/* 平板端适配 */
@media (max-width: 1024px) {
.container {
padding: 15px;
}
.header h1 {
font-size: 2.2rem;
}
.colors-grid {
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
}
}
/* 手机端适配 */
@media (max-width: 768px) {
.container {
padding: 10px;
}
.header {
margin-bottom: 25px;
padding: 20px 0;
}
.header h1 {
font-size: 1.8rem;
}
.subtitle {
font-size: 1rem;
}
.input-section {
padding: 20px;
}
.input-wrapper {
flex-direction: column;
align-items: stretch;
}
#colorPicker {
width: 100%;
height: 44px;
}
.colors-grid {
grid-template-columns: 1fr;
}
.color-details {
grid-template-columns: repeat(2, 1fr);
}
.palette {
padding: 20px;
}
.palette-name {
font-size: 1.2rem;
}
}
@media (max-width: 480px) {
.header h1 {
font-size: 1.6rem;
}
.input-section {
padding: 15px;
}
.palette {
padding: 15px;
}
.color-details {
grid-template-columns: 1fr;
}
}

View File

@@ -1,273 +1,273 @@
{
"code": 200,
"message": "获取成功。数据来自官方/权威源头,以确保稳定与实时。开源地址 https://github.com/vikiboss/60s反馈群 595941841",
"data": {
"input": {
"hex": "#DE4F99",
"rgb": {
"r": 222,
"g": 79,
"b": 153
},
"hsl": {
"h": 329,
"s": 68,
"l": 59
},
"name": "红色系"
},
"palettes": [
{
"name": "单色配色",
"description": "基于同一色相,通过调整明度和饱和度创建的和谐配色方案,适合营造统一、专业的视觉效果",
"colors": [
{
"hex": "#DE4F99",
"name": "主色",
"role": "primary",
"theory": "基础色相"
},
{
"hex": "#7C184C",
"name": "深色变体",
"role": "dark",
"theory": "降低明度"
},
{
"hex": "#EEA5CB",
"name": "浅色变体",
"role": "light",
"theory": "提高明度"
},
{
"hex": "#C96498",
"name": "柔和变体",
"role": "muted",
"theory": "降低饱和度"
},
{
"hex": "#ED4099",
"name": "鲜艳变体",
"role": "vibrant",
"theory": "提高饱和度"
}
]
},
{
"name": "互补配色",
"description": "使用色轮上相对的颜色,创造强烈对比和视觉冲击力,适用于需要突出重点的设计",
"colors": [
{
"hex": "#DE4F99",
"name": "主色",
"role": "primary",
"theory": "基础色相"
},
{
"hex": "#4FDE94",
"name": "互补色",
"role": "complementary",
"theory": "色轮对面 +180°"
},
{
"hex": "#F2BAD7",
"name": "主色浅调",
"role": "primary-light",
"theory": "主色提高明度"
},
{
"hex": "#BAF2D5",
"name": "互补色浅调",
"role": "complementary-light",
"theory": "互补色提高明度"
}
]
},
{
"name": "邻近配色",
"description": "使用色轮上相邻的颜色,创造自然和谐的渐变效果,常见于自然景观中",
"colors": [
{
"hex": "#DB4FDE",
"name": "邻近色1",
"role": "analogous-1",
"theory": "色相 -30°"
},
{
"hex": "#DE4F99",
"name": "主色",
"role": "primary",
"theory": "基础色相"
},
{
"hex": "#DE4F52",
"name": "邻近色2",
"role": "analogous-2",
"theory": "色相 +30°"
},
{
"hex": "#DE944F",
"name": "邻近色3",
"role": "analogous-3",
"theory": "色相 +60°"
}
]
},
{
"name": "三角配色",
"description": "在色轮上形成等边三角形的三种颜色,提供丰富对比的同时保持和谐平衡",
"colors": [
{
"hex": "#DE4F99",
"name": "主色",
"role": "primary",
"theory": "基础色相"
},
{
"hex": "#99DE4F",
"name": "三角色1",
"role": "triadic-1",
"theory": "色相 +120°"
},
{
"hex": "#4F99DE",
"name": "三角色2",
"role": "triadic-2",
"theory": "色相 +240°"
}
]
},
{
"name": "分裂互补配色",
"description": "使用互补色两侧的颜色,比纯互补配色更柔和,同时保持强烈的视觉对比",
"colors": [
{
"hex": "#DE4F99",
"name": "主色",
"role": "primary",
"theory": "基础色相"
},
{
"hex": "#52DE4F",
"name": "分裂互补色1",
"role": "split-comp-1",
"theory": "互补色 -30°"
},
{
"hex": "#4FDEDB",
"name": "分裂互补色2",
"role": "split-comp-2",
"theory": "互补色 +30°"
}
]
},
{
"name": "四边形配色",
"description": "在色轮上形成正方形的四种颜色,提供最丰富的颜色变化,适合复杂的设计项目",
"colors": [
{
"hex": "#DE4F99",
"name": "主色",
"role": "primary",
"theory": "基础色相"
},
{
"hex": "#DEDB4F",
"name": "四边形色1",
"role": "square-1",
"theory": "色相 +90°"
},
{
"hex": "#4FDE94",
"name": "四边形色2",
"role": "square-2",
"theory": "色相 +180°"
},
{
"hex": "#4F52DE",
"name": "四边形色3",
"role": "square-3",
"theory": "色相 +270°"
}
]
},
{
"name": "Web 设计配色",
"description": "专为 Web 界面设计优化的配色方案,考虑了可访问性和用户体验",
"colors": [
{
"hex": "#DE4F99",
"name": "品牌主色",
"role": "brand-primary",
"theory": "品牌识别色"
},
{
"hex": "#982F65",
"name": "按钮悬停",
"role": "hover-state",
"theory": "主色加深变体"
},
{
"hex": "#F6E9F0",
"name": "背景浅色",
"role": "background",
"theory": "高明度低饱和度"
},
{
"hex": "#1BDE7A",
"name": "强调色",
"role": "accent",
"theory": "互补色系强调"
},
{
"hex": "#6B7280",
"name": "文本辅助",
"role": "text-secondary",
"theory": "中性灰色文本"
}
]
},
{
"name": "暖色调配色",
"description": "基于暖色系的配色方案,营造温暖、活力和友好的氛围,适合餐饮、儿童产品等",
"colors": [
{
"hex": "#DE4F99",
"name": "主暖色",
"role": "warm-primary",
"theory": "暖色系基调"
},
{
"hex": "#DE4FC8",
"name": "暖色变体1",
"role": "warm-variant-1",
"theory": "暖色范围内调整"
},
{
"hex": "#DE4F5E",
"name": "暖色变体2",
"role": "warm-variant-2",
"theory": "暖色范围内调整"
},
{
"hex": "#EEA5CB",
"name": "暖色浅调",
"role": "warm-tint",
"theory": "提高明度的暖色"
}
]
}
],
"metadata": {
"color_theory": "基于色彩理论生成的专业配色方案",
"total_palettes": 8,
"applications": [
"Web 设计",
"UI/UX",
"品牌设计",
"室内设计",
"服装搭配"
]
}
}
{
"code": 200,
"message": "获取成功。数据来自官方/权威源头,以确保稳定与实时。开源地址 https://github.com/vikiboss/60s反馈群 595941841",
"data": {
"input": {
"hex": "#DE4F99",
"rgb": {
"r": 222,
"g": 79,
"b": 153
},
"hsl": {
"h": 329,
"s": 68,
"l": 59
},
"name": "红色系"
},
"palettes": [
{
"name": "单色配色",
"description": "基于同一色相,通过调整明度和饱和度创建的和谐配色方案,适合营造统一、专业的视觉效果",
"colors": [
{
"hex": "#DE4F99",
"name": "主色",
"role": "primary",
"theory": "基础色相"
},
{
"hex": "#7C184C",
"name": "深色变体",
"role": "dark",
"theory": "降低明度"
},
{
"hex": "#EEA5CB",
"name": "浅色变体",
"role": "light",
"theory": "提高明度"
},
{
"hex": "#C96498",
"name": "柔和变体",
"role": "muted",
"theory": "降低饱和度"
},
{
"hex": "#ED4099",
"name": "鲜艳变体",
"role": "vibrant",
"theory": "提高饱和度"
}
]
},
{
"name": "互补配色",
"description": "使用色轮上相对的颜色,创造强烈对比和视觉冲击力,适用于需要突出重点的设计",
"colors": [
{
"hex": "#DE4F99",
"name": "主色",
"role": "primary",
"theory": "基础色相"
},
{
"hex": "#4FDE94",
"name": "互补色",
"role": "complementary",
"theory": "色轮对面 +180°"
},
{
"hex": "#F2BAD7",
"name": "主色浅调",
"role": "primary-light",
"theory": "主色提高明度"
},
{
"hex": "#BAF2D5",
"name": "互补色浅调",
"role": "complementary-light",
"theory": "互补色提高明度"
}
]
},
{
"name": "邻近配色",
"description": "使用色轮上相邻的颜色,创造自然和谐的渐变效果,常见于自然景观中",
"colors": [
{
"hex": "#DB4FDE",
"name": "邻近色1",
"role": "analogous-1",
"theory": "色相 -30°"
},
{
"hex": "#DE4F99",
"name": "主色",
"role": "primary",
"theory": "基础色相"
},
{
"hex": "#DE4F52",
"name": "邻近色2",
"role": "analogous-2",
"theory": "色相 +30°"
},
{
"hex": "#DE944F",
"name": "邻近色3",
"role": "analogous-3",
"theory": "色相 +60°"
}
]
},
{
"name": "三角配色",
"description": "在色轮上形成等边三角形的三种颜色,提供丰富对比的同时保持和谐平衡",
"colors": [
{
"hex": "#DE4F99",
"name": "主色",
"role": "primary",
"theory": "基础色相"
},
{
"hex": "#99DE4F",
"name": "三角色1",
"role": "triadic-1",
"theory": "色相 +120°"
},
{
"hex": "#4F99DE",
"name": "三角色2",
"role": "triadic-2",
"theory": "色相 +240°"
}
]
},
{
"name": "分裂互补配色",
"description": "使用互补色两侧的颜色,比纯互补配色更柔和,同时保持强烈的视觉对比",
"colors": [
{
"hex": "#DE4F99",
"name": "主色",
"role": "primary",
"theory": "基础色相"
},
{
"hex": "#52DE4F",
"name": "分裂互补色1",
"role": "split-comp-1",
"theory": "互补色 -30°"
},
{
"hex": "#4FDEDB",
"name": "分裂互补色2",
"role": "split-comp-2",
"theory": "互补色 +30°"
}
]
},
{
"name": "四边形配色",
"description": "在色轮上形成正方形的四种颜色,提供最丰富的颜色变化,适合复杂的设计项目",
"colors": [
{
"hex": "#DE4F99",
"name": "主色",
"role": "primary",
"theory": "基础色相"
},
{
"hex": "#DEDB4F",
"name": "四边形色1",
"role": "square-1",
"theory": "色相 +90°"
},
{
"hex": "#4FDE94",
"name": "四边形色2",
"role": "square-2",
"theory": "色相 +180°"
},
{
"hex": "#4F52DE",
"name": "四边形色3",
"role": "square-3",
"theory": "色相 +270°"
}
]
},
{
"name": "Web 设计配色",
"description": "专为 Web 界面设计优化的配色方案,考虑了可访问性和用户体验",
"colors": [
{
"hex": "#DE4F99",
"name": "品牌主色",
"role": "brand-primary",
"theory": "品牌识别色"
},
{
"hex": "#982F65",
"name": "按钮悬停",
"role": "hover-state",
"theory": "主色加深变体"
},
{
"hex": "#F6E9F0",
"name": "背景浅色",
"role": "background",
"theory": "高明度低饱和度"
},
{
"hex": "#1BDE7A",
"name": "强调色",
"role": "accent",
"theory": "互补色系强调"
},
{
"hex": "#6B7280",
"name": "文本辅助",
"role": "text-secondary",
"theory": "中性灰色文本"
}
]
},
{
"name": "暖色调配色",
"description": "基于暖色系的配色方案,营造温暖、活力和友好的氛围,适合餐饮、儿童产品等",
"colors": [
{
"hex": "#DE4F99",
"name": "主暖色",
"role": "warm-primary",
"theory": "暖色系基调"
},
{
"hex": "#DE4FC8",
"name": "暖色变体1",
"role": "warm-variant-1",
"theory": "暖色范围内调整"
},
{
"hex": "#DE4F5E",
"name": "暖色变体2",
"role": "warm-variant-2",
"theory": "暖色范围内调整"
},
{
"hex": "#EEA5CB",
"name": "暖色浅调",
"role": "warm-tint",
"theory": "提高明度的暖色"
}
]
}
],
"metadata": {
"color_theory": "基于色彩理论生成的专业配色方案",
"total_palettes": 8,
"applications": [
"Web 设计",
"UI/UX",
"品牌设计",
"室内设计",
"服装搭配"
]
}
}
}

View File

@@ -1,232 +1,232 @@
/* 高维度背景特效样式 - 神秘高级风格 */
/* 背景容器 */
.background-container {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1;
overflow: hidden;
pointer-events: none;
background: radial-gradient(ellipse at center,
rgba(15, 0, 30, 0.95) 0%,
rgba(5, 0, 15, 0.98) 50%,
rgba(0, 0, 0, 1) 100%);
}
/* 几何网格层 */
.geometric-grid {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image:
linear-gradient(rgba(138, 43, 226, 0.1) 1px, transparent 1px),
linear-gradient(90deg, rgba(138, 43, 226, 0.1) 1px, transparent 1px),
linear-gradient(rgba(75, 0, 130, 0.05) 1px, transparent 1px),
linear-gradient(90deg, rgba(75, 0, 130, 0.05) 1px, transparent 1px);
background-size: 100px 100px, 100px 100px, 20px 20px, 20px 20px;
animation: gridPulse 8s ease-in-out infinite;
}
@keyframes gridPulse {
0%, 100% { opacity: 0.3; transform: scale(1); }
50% { opacity: 0.6; transform: scale(1.02); }
}
/* 神经网络效果 */
.neural-network {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background:
radial-gradient(circle at 20% 30%, rgba(138, 43, 226, 0.15) 2px, transparent 2px),
radial-gradient(circle at 80% 20%, rgba(75, 0, 130, 0.12) 1px, transparent 1px),
radial-gradient(circle at 40% 70%, rgba(147, 0, 211, 0.1) 1.5px, transparent 1.5px),
radial-gradient(circle at 90% 80%, rgba(138, 43, 226, 0.08) 1px, transparent 1px),
radial-gradient(circle at 10% 90%, rgba(75, 0, 130, 0.1) 2px, transparent 2px);
background-size: 200px 200px, 150px 150px, 300px 300px, 180px 180px, 250px 250px;
animation: neuralFlow 15s linear infinite;
}
@keyframes neuralFlow {
0% { transform: translate(0, 0) rotate(0deg); }
25% { transform: translate(-10px, -5px) rotate(90deg); }
50% { transform: translate(-5px, -10px) rotate(180deg); }
75% { transform: translate(5px, -5px) rotate(270deg); }
100% { transform: translate(0, 0) rotate(360deg); }
}
/* 粒子系统 */
.particle-system {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image:
radial-gradient(circle, rgba(138, 43, 226, 0.4) 1px, transparent 1px),
radial-gradient(circle, rgba(75, 0, 130, 0.3) 0.5px, transparent 0.5px),
radial-gradient(circle, rgba(147, 0, 211, 0.2) 0.8px, transparent 0.8px);
background-size: 80px 80px, 120px 120px, 160px 160px;
background-position: 0 0, 40px 40px, 80px 80px;
animation: particleFloat 20s ease-in-out infinite;
}
@keyframes particleFloat {
0%, 100% { transform: translateY(0px) translateX(0px); }
25% { transform: translateY(-20px) translateX(10px); }
50% { transform: translateY(-10px) translateX(-15px); }
75% { transform: translateY(-30px) translateX(5px); }
}
/* 扫描线效果 */
.scan-lines {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: repeating-linear-gradient(
0deg,
transparent 0px,
transparent 2px,
rgba(138, 43, 226, 0.03) 2px,
rgba(138, 43, 226, 0.03) 4px
);
animation: scanMove 3s linear infinite;
}
@keyframes scanMove {
0% { transform: translateY(-100%); }
100% { transform: translateY(100%); }
}
/* 全息投影效果 */
.holographic-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background:
linear-gradient(45deg,
transparent 30%,
rgba(138, 43, 226, 0.05) 50%,
transparent 70%),
linear-gradient(-45deg,
transparent 30%,
rgba(75, 0, 130, 0.03) 50%,
transparent 70%);
background-size: 200px 200px, 150px 150px;
animation: holographicShift 12s ease-in-out infinite;
}
@keyframes holographicShift {
0%, 100% {
background-position: 0% 0%, 100% 100%;
opacity: 0.7;
}
50% {
background-position: 100% 100%, 0% 0%;
opacity: 1;
}
}
/* 数据流效果 */
.data-stream {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background:
linear-gradient(90deg,
transparent 0%,
rgba(138, 43, 226, 0.1) 50%,
transparent 100%);
background-size: 300px 100%;
animation: dataFlow 8s linear infinite;
}
@keyframes dataFlow {
0% { transform: translateX(-100%); }
100% { transform: translateX(100%); }
}
/* 量子波动效果 */
.quantum-waves {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background:
radial-gradient(ellipse 200px 100px at 50% 0%,
rgba(138, 43, 226, 0.1) 0%,
transparent 50%),
radial-gradient(ellipse 300px 150px at 50% 100%,
rgba(75, 0, 130, 0.08) 0%,
transparent 50%);
animation: quantumPulse 10s ease-in-out infinite;
}
@keyframes quantumPulse {
0%, 100% {
transform: scale(1) rotate(0deg);
opacity: 0.5;
}
50% {
transform: scale(1.1) rotate(180deg);
opacity: 0.8;
}
}
/* 响应式优化 */
@media (max-width: 768px) {
.geometric-grid {
background-size: 50px 50px, 50px 50px, 10px 10px, 10px 10px;
}
.neural-network {
background-size: 100px 100px, 75px 75px, 150px 150px, 90px 90px, 125px 125px;
}
.particle-system {
background-size: 40px 40px, 60px 60px, 80px 80px;
}
}
/* 减少动画偏好 */
@media (prefers-reduced-motion: reduce) {
.geometric-grid,
.neural-network,
.particle-system,
.scan-lines,
.holographic-overlay,
.data-stream,
.quantum-waves {
animation: none;
}
}
/* 高对比度模式 */
@media (prefers-contrast: high) {
.background-container {
background: radial-gradient(ellipse at center,
rgba(25, 0, 50, 0.95) 0%,
rgba(10, 0, 25, 0.98) 50%,
rgba(0, 0, 0, 1) 100%);
}
.geometric-grid {
background-image:
linear-gradient(rgba(200, 100, 255, 0.2) 1px, transparent 1px),
linear-gradient(90deg, rgba(200, 100, 255, 0.2) 1px, transparent 1px);
}
/* 高维度背景特效样式 - 神秘高级风格 */
/* 背景容器 */
.background-container {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1;
overflow: hidden;
pointer-events: none;
background: radial-gradient(ellipse at center,
rgba(15, 0, 30, 0.95) 0%,
rgba(5, 0, 15, 0.98) 50%,
rgba(0, 0, 0, 1) 100%);
}
/* 几何网格层 */
.geometric-grid {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image:
linear-gradient(rgba(138, 43, 226, 0.1) 1px, transparent 1px),
linear-gradient(90deg, rgba(138, 43, 226, 0.1) 1px, transparent 1px),
linear-gradient(rgba(75, 0, 130, 0.05) 1px, transparent 1px),
linear-gradient(90deg, rgba(75, 0, 130, 0.05) 1px, transparent 1px);
background-size: 100px 100px, 100px 100px, 20px 20px, 20px 20px;
animation: gridPulse 8s ease-in-out infinite;
}
@keyframes gridPulse {
0%, 100% { opacity: 0.3; transform: scale(1); }
50% { opacity: 0.6; transform: scale(1.02); }
}
/* 神经网络效果 */
.neural-network {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background:
radial-gradient(circle at 20% 30%, rgba(138, 43, 226, 0.15) 2px, transparent 2px),
radial-gradient(circle at 80% 20%, rgba(75, 0, 130, 0.12) 1px, transparent 1px),
radial-gradient(circle at 40% 70%, rgba(147, 0, 211, 0.1) 1.5px, transparent 1.5px),
radial-gradient(circle at 90% 80%, rgba(138, 43, 226, 0.08) 1px, transparent 1px),
radial-gradient(circle at 10% 90%, rgba(75, 0, 130, 0.1) 2px, transparent 2px);
background-size: 200px 200px, 150px 150px, 300px 300px, 180px 180px, 250px 250px;
animation: neuralFlow 15s linear infinite;
}
@keyframes neuralFlow {
0% { transform: translate(0, 0) rotate(0deg); }
25% { transform: translate(-10px, -5px) rotate(90deg); }
50% { transform: translate(-5px, -10px) rotate(180deg); }
75% { transform: translate(5px, -5px) rotate(270deg); }
100% { transform: translate(0, 0) rotate(360deg); }
}
/* 粒子系统 */
.particle-system {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image:
radial-gradient(circle, rgba(138, 43, 226, 0.4) 1px, transparent 1px),
radial-gradient(circle, rgba(75, 0, 130, 0.3) 0.5px, transparent 0.5px),
radial-gradient(circle, rgba(147, 0, 211, 0.2) 0.8px, transparent 0.8px);
background-size: 80px 80px, 120px 120px, 160px 160px;
background-position: 0 0, 40px 40px, 80px 80px;
animation: particleFloat 20s ease-in-out infinite;
}
@keyframes particleFloat {
0%, 100% { transform: translateY(0px) translateX(0px); }
25% { transform: translateY(-20px) translateX(10px); }
50% { transform: translateY(-10px) translateX(-15px); }
75% { transform: translateY(-30px) translateX(5px); }
}
/* 扫描线效果 */
.scan-lines {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: repeating-linear-gradient(
0deg,
transparent 0px,
transparent 2px,
rgba(138, 43, 226, 0.03) 2px,
rgba(138, 43, 226, 0.03) 4px
);
animation: scanMove 3s linear infinite;
}
@keyframes scanMove {
0% { transform: translateY(-100%); }
100% { transform: translateY(100%); }
}
/* 全息投影效果 */
.holographic-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background:
linear-gradient(45deg,
transparent 30%,
rgba(138, 43, 226, 0.05) 50%,
transparent 70%),
linear-gradient(-45deg,
transparent 30%,
rgba(75, 0, 130, 0.03) 50%,
transparent 70%);
background-size: 200px 200px, 150px 150px;
animation: holographicShift 12s ease-in-out infinite;
}
@keyframes holographicShift {
0%, 100% {
background-position: 0% 0%, 100% 100%;
opacity: 0.7;
}
50% {
background-position: 100% 100%, 0% 0%;
opacity: 1;
}
}
/* 数据流效果 */
.data-stream {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background:
linear-gradient(90deg,
transparent 0%,
rgba(138, 43, 226, 0.1) 50%,
transparent 100%);
background-size: 300px 100%;
animation: dataFlow 8s linear infinite;
}
@keyframes dataFlow {
0% { transform: translateX(-100%); }
100% { transform: translateX(100%); }
}
/* 量子波动效果 */
.quantum-waves {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background:
radial-gradient(ellipse 200px 100px at 50% 0%,
rgba(138, 43, 226, 0.1) 0%,
transparent 50%),
radial-gradient(ellipse 300px 150px at 50% 100%,
rgba(75, 0, 130, 0.08) 0%,
transparent 50%);
animation: quantumPulse 10s ease-in-out infinite;
}
@keyframes quantumPulse {
0%, 100% {
transform: scale(1) rotate(0deg);
opacity: 0.5;
}
50% {
transform: scale(1.1) rotate(180deg);
opacity: 0.8;
}
}
/* 响应式优化 */
@media (max-width: 768px) {
.geometric-grid {
background-size: 50px 50px, 50px 50px, 10px 10px, 10px 10px;
}
.neural-network {
background-size: 100px 100px, 75px 75px, 150px 150px, 90px 90px, 125px 125px;
}
.particle-system {
background-size: 40px 40px, 60px 60px, 80px 80px;
}
}
/* 减少动画偏好 */
@media (prefers-reduced-motion: reduce) {
.geometric-grid,
.neural-network,
.particle-system,
.scan-lines,
.holographic-overlay,
.data-stream,
.quantum-waves {
animation: none;
}
}
/* 高对比度模式 */
@media (prefers-contrast: high) {
.background-container {
background: radial-gradient(ellipse at center,
rgba(25, 0, 50, 0.95) 0%,
rgba(10, 0, 25, 0.98) 50%,
rgba(0, 0, 0, 1) 100%);
}
.geometric-grid {
background-image:
linear-gradient(rgba(200, 100, 255, 0.2) 1px, transparent 1px),
linear-gradient(90deg, rgba(200, 100, 255, 0.2) 1px, transparent 1px);
}
}

View File

@@ -1,227 +1,227 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>链接OG信息查询 - 神秘解析器</title>
<meta name="description" content="高级链接OG信息查询工具解析网页元数据">
<link rel="stylesheet" href="css/style.css">
<link rel="stylesheet" href="css/background.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
</head>
<body>
<!-- 背景特效容器 -->
<div class="background-container">
<div class="matrix-rain"></div>
<div class="geometric-shapes"></div>
<div class="neural-network"></div>
</div>
<!-- 主容器 -->
<div class="main-container">
<!-- 头部区域 -->
<header class="header">
<div class="header-content">
<div class="logo-section">
<i class="fas fa-link logo-icon"></i>
<h1 class="title">OG 解析器</h1>
<span class="subtitle">链接元数据神秘解析</span>
</div>
<div class="status-indicator">
<div class="pulse-dot"></div>
<span class="status-text">系统就绪</span>
</div>
</div>
</header>
<!-- 查询区域 -->
<section class="query-section">
<div class="input-container">
<div class="input-wrapper">
<i class="fas fa-globe input-icon"></i>
<input type="url" id="url-input" placeholder="输入链接地址进行深度解析..." class="url-input">
<div class="input-border"></div>
</div>
<button id="analyze-btn" class="analyze-btn">
<span class="btn-text">开始解析</span>
<div class="btn-effects">
<div class="ripple"></div>
<div class="glow"></div>
</div>
</button>
</div>
</section>
<!-- 加载状态 -->
<div id="loading" class="loading-container" style="display: none;">
<div class="loading-content">
<div class="scanner">
<div class="scanner-line"></div>
<div class="scanner-grid">
<div class="grid-line"></div>
<div class="grid-line"></div>
<div class="grid-line"></div>
<div class="grid-line"></div>
</div>
</div>
<div class="loading-text">
<span class="loading-title">正在解析链接</span>
<span class="loading-subtitle">深度扫描元数据中...</span>
</div>
</div>
</div>
<!-- 结果展示区域 -->
<section id="results" class="results-section" style="display: none;">
<div class="results-header">
<h2 class="results-title">
<i class="fas fa-chart-network"></i>
解析结果
</h2>
<div class="results-actions">
<button id="copy-btn" class="action-btn">
<i class="fas fa-copy"></i>
<span>复制数据</span>
</button>
<button id="clear-btn" class="action-btn">
<i class="fas fa-trash"></i>
<span>清除结果</span>
</button>
</div>
</div>
<div class="og-card">
<!-- 基础信息 -->
<div class="info-section basic-info">
<div class="section-header">
<i class="fas fa-info-circle"></i>
<span>基础信息</span>
</div>
<div class="info-grid">
<div class="info-item">
<label>标题</label>
<div id="og-title" class="info-value">-</div>
</div>
<div class="info-item">
<label>描述</label>
<div id="og-description" class="info-value">-</div>
</div>
<div class="info-item">
<label>网站名称</label>
<div id="og-site-name" class="info-value">-</div>
</div>
<div class="info-item">
<label>类型</label>
<div id="og-type" class="info-value">-</div>
</div>
</div>
</div>
<!-- 媒体信息 -->
<div class="info-section media-info">
<div class="section-header">
<i class="fas fa-image"></i>
<span>媒体信息</span>
</div>
<div class="media-preview" id="media-preview">
<div class="no-media">
<i class="fas fa-image-slash"></i>
<span>暂无媒体内容</span>
</div>
</div>
<div class="media-details">
<div class="info-item">
<label>图片URL</label>
<div id="og-image" class="info-value url-value">-</div>
</div>
<div class="info-item">
<label>图片描述</label>
<div id="og-image-alt" class="info-value">-</div>
</div>
</div>
</div>
<!-- 技术信息 -->
<div class="info-section tech-info">
<div class="section-header">
<i class="fas fa-code"></i>
<span>技术信息</span>
</div>
<div class="info-grid">
<div class="info-item">
<label>URL</label>
<div id="og-url" class="info-value url-value">-</div>
</div>
<div class="info-item">
<label>域名</label>
<div id="og-domain" class="info-value">-</div>
</div>
<div class="info-item">
<label>语言</label>
<div id="og-locale" class="info-value">-</div>
</div>
<div class="info-item">
<label>更新时间</label>
<div id="og-updated-time" class="info-value">-</div>
</div>
<div class="info-item">
<label>响应时间</label>
<div id="response-time" class="info-value">-</div>
</div>
</div>
</div>
</div>
</section>
<!-- 错误信息 -->
<div id="error" class="error-container" style="display: none;">
<div class="error-content">
<div class="error-icon">
<i class="fas fa-exclamation-triangle"></i>
</div>
<div class="error-text">
<h3 class="error-title">解析失败</h3>
<p id="error-message" class="error-message">未知错误</p>
</div>
<button id="retryBtn" class="retry-btn">
<i class="fas fa-redo"></i>
<span>重新尝试</span>
</button>
</div>
</div>
</div>
<!-- 提示消息 -->
<div id="tip-message" class="tip-container">
<div class="tip-content">
<i class="fas fa-lightbulb tip-icon"></i>
<span class="tip-text"></span>
</div>
</div>
<!-- Toast消息 -->
<div id="toast" class="toast-container">
<div class="toast-content">
<i class="toast-icon"></i>
<span class="toast-message"></span>
</div>
</div>
<!-- 页脚 -->
<footer class="footer">
<div class="footer-content">
<p class="footer-text">
<i class="fas fa-shield-alt"></i>
高级链接解析系统 | 神秘数据挖掘
</p>
<div class="footer-links">
<span class="footer-link">隐私保护</span>
<span class="footer-divider">|</span>
<span class="footer-link">安全解析</span>
</div>
</div>
</footer>
<script src="js/script.js"></script>
</body>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>链接OG信息查询 - 神秘解析器</title>
<meta name="description" content="高级链接OG信息查询工具解析网页元数据">
<link rel="stylesheet" href="css/style.css">
<link rel="stylesheet" href="css/background.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
</head>
<body>
<!-- 背景特效容器 -->
<div class="background-container">
<div class="matrix-rain"></div>
<div class="geometric-shapes"></div>
<div class="neural-network"></div>
</div>
<!-- 主容器 -->
<div class="main-container">
<!-- 头部区域 -->
<header class="header">
<div class="header-content">
<div class="logo-section">
<i class="fas fa-link logo-icon"></i>
<h1 class="title">OG 解析器</h1>
<span class="subtitle">链接元数据神秘解析</span>
</div>
<div class="status-indicator">
<div class="pulse-dot"></div>
<span class="status-text">系统就绪</span>
</div>
</div>
</header>
<!-- 查询区域 -->
<section class="query-section">
<div class="input-container">
<div class="input-wrapper">
<i class="fas fa-globe input-icon"></i>
<input type="url" id="url-input" placeholder="输入链接地址进行深度解析..." class="url-input">
<div class="input-border"></div>
</div>
<button id="analyze-btn" class="analyze-btn">
<span class="btn-text">开始解析</span>
<div class="btn-effects">
<div class="ripple"></div>
<div class="glow"></div>
</div>
</button>
</div>
</section>
<!-- 加载状态 -->
<div id="loading" class="loading-container" style="display: none;">
<div class="loading-content">
<div class="scanner">
<div class="scanner-line"></div>
<div class="scanner-grid">
<div class="grid-line"></div>
<div class="grid-line"></div>
<div class="grid-line"></div>
<div class="grid-line"></div>
</div>
</div>
<div class="loading-text">
<span class="loading-title">正在解析链接</span>
<span class="loading-subtitle">深度扫描元数据中...</span>
</div>
</div>
</div>
<!-- 结果展示区域 -->
<section id="results" class="results-section" style="display: none;">
<div class="results-header">
<h2 class="results-title">
<i class="fas fa-chart-network"></i>
解析结果
</h2>
<div class="results-actions">
<button id="copy-btn" class="action-btn">
<i class="fas fa-copy"></i>
<span>复制数据</span>
</button>
<button id="clear-btn" class="action-btn">
<i class="fas fa-trash"></i>
<span>清除结果</span>
</button>
</div>
</div>
<div class="og-card">
<!-- 基础信息 -->
<div class="info-section basic-info">
<div class="section-header">
<i class="fas fa-info-circle"></i>
<span>基础信息</span>
</div>
<div class="info-grid">
<div class="info-item">
<label>标题</label>
<div id="og-title" class="info-value">-</div>
</div>
<div class="info-item">
<label>描述</label>
<div id="og-description" class="info-value">-</div>
</div>
<div class="info-item">
<label>网站名称</label>
<div id="og-site-name" class="info-value">-</div>
</div>
<div class="info-item">
<label>类型</label>
<div id="og-type" class="info-value">-</div>
</div>
</div>
</div>
<!-- 媒体信息 -->
<div class="info-section media-info">
<div class="section-header">
<i class="fas fa-image"></i>
<span>媒体信息</span>
</div>
<div class="media-preview" id="media-preview">
<div class="no-media">
<i class="fas fa-image-slash"></i>
<span>暂无媒体内容</span>
</div>
</div>
<div class="media-details">
<div class="info-item">
<label>图片URL</label>
<div id="og-image" class="info-value url-value">-</div>
</div>
<div class="info-item">
<label>图片描述</label>
<div id="og-image-alt" class="info-value">-</div>
</div>
</div>
</div>
<!-- 技术信息 -->
<div class="info-section tech-info">
<div class="section-header">
<i class="fas fa-code"></i>
<span>技术信息</span>
</div>
<div class="info-grid">
<div class="info-item">
<label>URL</label>
<div id="og-url" class="info-value url-value">-</div>
</div>
<div class="info-item">
<label>域名</label>
<div id="og-domain" class="info-value">-</div>
</div>
<div class="info-item">
<label>语言</label>
<div id="og-locale" class="info-value">-</div>
</div>
<div class="info-item">
<label>更新时间</label>
<div id="og-updated-time" class="info-value">-</div>
</div>
<div class="info-item">
<label>响应时间</label>
<div id="response-time" class="info-value">-</div>
</div>
</div>
</div>
</div>
</section>
<!-- 错误信息 -->
<div id="error" class="error-container" style="display: none;">
<div class="error-content">
<div class="error-icon">
<i class="fas fa-exclamation-triangle"></i>
</div>
<div class="error-text">
<h3 class="error-title">解析失败</h3>
<p id="error-message" class="error-message">未知错误</p>
</div>
<button id="retryBtn" class="retry-btn">
<i class="fas fa-redo"></i>
<span>重新尝试</span>
</button>
</div>
</div>
</div>
<!-- 提示消息 -->
<div id="tip-message" class="tip-container">
<div class="tip-content">
<i class="fas fa-lightbulb tip-icon"></i>
<span class="tip-text"></span>
</div>
</div>
<!-- Toast消息 -->
<div id="toast" class="toast-container">
<div class="toast-content">
<i class="toast-icon"></i>
<span class="toast-message"></span>
</div>
</div>
<!-- 页脚 -->
<footer class="footer">
<div class="footer-content">
<p class="footer-text">
<i class="fas fa-shield-alt"></i>
高级链接解析系统 | 神秘数据挖掘
</p>
<div class="footer-links">
<span class="footer-link">隐私保护</span>
<span class="footer-divider">|</span>
<span class="footer-link">安全解析</span>
</div>
</div>
</footer>
<script src="js/script.js"></script>
</body>
</html>

View File

@@ -1,3 +1,3 @@
[
"https://60s.api.shumengya.top"
]
[
"https://60s.api.shumengya.top"
]

View File

@@ -1,66 +1,66 @@
{
"code": 200,
"message": "success",
"data": {
"url": "https://example.com",
"title": "示例网站标题",
"description": "这是一个示例网站的描述信息用于展示OG标签解析功能。",
"image": "https://example.com/og-image.jpg",
"site_name": "示例网站",
"type": "website",
"locale": "zh_CN",
"author": "网站作者",
"keywords": "示例,网站,OG标签,元数据",
"favicon": "https://example.com/favicon.ico",
"canonical_url": "https://example.com",
"robots": "index,follow",
"viewport": "width=device-width, initial-scale=1.0",
"charset": "UTF-8",
"language": "zh-CN",
"published_time": "2024-01-01T00:00:00Z",
"modified_time": "2024-01-15T12:30:00Z",
"section": "技术",
"tags": ["前端", "元数据", "SEO"],
"twitter": {
"card": "summary_large_image",
"site": "@example",
"creator": "@author",
"title": "Twitter标题",
"description": "Twitter描述",
"image": "https://example.com/twitter-image.jpg"
},
"facebook": {
"app_id": "123456789",
"admins": "987654321"
},
"structured_data": {
"@context": "https://schema.org",
"@type": "WebPage",
"name": "示例网页",
"description": "示例网页描述",
"url": "https://example.com"
},
"meta_tags": {
"generator": "WordPress 6.0",
"theme-color": "#000000",
"msapplication-TileColor": "#ffffff",
"apple-mobile-web-app-capable": "yes",
"apple-mobile-web-app-status-bar-style": "default"
},
"performance": {
"load_time": 1.25,
"page_size": "2.3MB",
"requests_count": 45
},
"seo_score": {
"overall": 85,
"title_score": 90,
"description_score": 80,
"image_score": 85,
"structure_score": 88
}
},
"timestamp": "2024-01-15T12:30:45Z",
"request_id": "req_123456789",
"processing_time": 0.85
{
"code": 200,
"message": "success",
"data": {
"url": "https://example.com",
"title": "示例网站标题",
"description": "这是一个示例网站的描述信息用于展示OG标签解析功能。",
"image": "https://example.com/og-image.jpg",
"site_name": "示例网站",
"type": "website",
"locale": "zh_CN",
"author": "网站作者",
"keywords": "示例,网站,OG标签,元数据",
"favicon": "https://example.com/favicon.ico",
"canonical_url": "https://example.com",
"robots": "index,follow",
"viewport": "width=device-width, initial-scale=1.0",
"charset": "UTF-8",
"language": "zh-CN",
"published_time": "2024-01-01T00:00:00Z",
"modified_time": "2024-01-15T12:30:00Z",
"section": "技术",
"tags": ["前端", "元数据", "SEO"],
"twitter": {
"card": "summary_large_image",
"site": "@example",
"creator": "@author",
"title": "Twitter标题",
"description": "Twitter描述",
"image": "https://example.com/twitter-image.jpg"
},
"facebook": {
"app_id": "123456789",
"admins": "987654321"
},
"structured_data": {
"@context": "https://schema.org",
"@type": "WebPage",
"name": "示例网页",
"description": "示例网页描述",
"url": "https://example.com"
},
"meta_tags": {
"generator": "WordPress 6.0",
"theme-color": "#000000",
"msapplication-TileColor": "#ffffff",
"apple-mobile-web-app-capable": "yes",
"apple-mobile-web-app-status-bar-style": "default"
},
"performance": {
"load_time": 1.25,
"page_size": "2.3MB",
"requests_count": 45
},
"seo_score": {
"overall": 85,
"title_score": 90,
"description_score": 80,
"image_score": 85,
"structure_score": 88
}
},
"timestamp": "2024-01-15T12:30:45Z",
"request_id": "req_123456789",
"processing_time": 0.85
}

View File

@@ -1,252 +1,252 @@
/* 背景样式文件 */
/* 主背景渐变 */
body {
background: linear-gradient(135deg, #e8f5e8 0%, #f0f9f0 25%, #f8fdf8 50%, #e8f5e8 75%, #f0f9f0 100%);
background-size: 400% 400%;
animation: gradientShift 15s ease infinite;
position: relative;
overflow-x: hidden;
}
/* 背景渐变动画 */
@keyframes gradientShift {
0% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
}
/* 装饰性背景元素 */
body::before {
content: '';
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image:
radial-gradient(circle at 20% 80%, rgba(76, 175, 80, 0.1) 0%, transparent 50%),
radial-gradient(circle at 80% 20%, rgba(45, 90, 61, 0.08) 0%, transparent 50%),
radial-gradient(circle at 40% 40%, rgba(76, 175, 80, 0.05) 0%, transparent 50%);
pointer-events: none;
z-index: -2;
}
/* 浮动装饰圆点 */
body::after {
content: '';
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image:
radial-gradient(2px 2px at 20px 30px, rgba(76, 175, 80, 0.3), transparent),
radial-gradient(2px 2px at 40px 70px, rgba(45, 90, 61, 0.2), transparent),
radial-gradient(1px 1px at 90px 40px, rgba(76, 175, 80, 0.4), transparent),
radial-gradient(1px 1px at 130px 80px, rgba(45, 90, 61, 0.3), transparent),
radial-gradient(2px 2px at 160px 30px, rgba(76, 175, 80, 0.2), transparent);
background-repeat: repeat;
background-size: 200px 100px;
animation: floatDots 20s linear infinite;
pointer-events: none;
z-index: -1;
opacity: 0.6;
}
/* 圆点浮动动画 */
@keyframes floatDots {
0% {
transform: translateY(0px) translateX(0px);
}
33% {
transform: translateY(-10px) translateX(5px);
}
66% {
transform: translateY(5px) translateX(-5px);
}
100% {
transform: translateY(0px) translateX(0px);
}
}
/* 网格背景(可选,默认隐藏) */
.grid-background {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image:
linear-gradient(rgba(76, 175, 80, 0.03) 1px, transparent 1px),
linear-gradient(90deg, rgba(76, 175, 80, 0.03) 1px, transparent 1px);
background-size: 50px 50px;
pointer-events: none;
z-index: -3;
opacity: 0;
transition: opacity 0.3s ease;
}
.grid-background.active {
opacity: 1;
}
/* 响应式背景调整 */
@media (max-width: 768px) {
body::after {
background-size: 150px 75px;
animation-duration: 25s;
}
body::before {
background-image:
radial-gradient(circle at 30% 70%, rgba(76, 175, 80, 0.08) 0%, transparent 50%),
radial-gradient(circle at 70% 30%, rgba(45, 90, 61, 0.06) 0%, transparent 50%);
}
}
@media (max-width: 480px) {
body {
animation-duration: 20s;
}
body::after {
background-size: 100px 50px;
opacity: 0.4;
}
}
/* 高对比度模式下的背景调整 */
@media (prefers-contrast: high) {
body {
background: #f8fdf8;
animation: none;
}
body::before,
body::after {
display: none;
}
}
/* 减少动画模式下的背景调整 */
@media (prefers-reduced-motion: reduce) {
body {
animation: none;
background: linear-gradient(135deg, #e8f5e8 0%, #f0f9f0 50%, #f8fdf8 100%);
}
body::after {
animation: none;
}
@keyframes gradientShift {
0%, 100% {
background-position: 0% 50%;
}
}
@keyframes floatDots {
0%, 100% {
transform: translateY(0px) translateX(0px);
}
}
}
/* 深色模式支持 */
@media (prefers-color-scheme: dark) {
body {
background: linear-gradient(135deg, #1a2e1a 0%, #2d4a2d 25%, #1f3a1f 50%, #1a2e1a 75%, #2d4a2d 100%);
}
body::before {
background-image:
radial-gradient(circle at 20% 80%, rgba(76, 175, 80, 0.15) 0%, transparent 50%),
radial-gradient(circle at 80% 20%, rgba(144, 238, 144, 0.1) 0%, transparent 50%),
radial-gradient(circle at 40% 40%, rgba(76, 175, 80, 0.08) 0%, transparent 50%);
}
body::after {
background-image:
radial-gradient(2px 2px at 20px 30px, rgba(144, 238, 144, 0.4), transparent),
radial-gradient(2px 2px at 40px 70px, rgba(76, 175, 80, 0.3), transparent),
radial-gradient(1px 1px at 90px 40px, rgba(144, 238, 144, 0.5), transparent),
radial-gradient(1px 1px at 130px 80px, rgba(76, 175, 80, 0.4), transparent),
radial-gradient(2px 2px at 160px 30px, rgba(144, 238, 144, 0.3), transparent);
}
}
/* 打印样式 */
@media print {
body {
background: white !important;
animation: none !important;
}
body::before,
body::after {
display: none !important;
}
}
/* 特殊效果:鼠标悬停时的背景变化 */
@media (hover: hover) {
.container:hover {
position: relative;
}
.container:hover::before {
content: '';
position: absolute;
top: -20px;
left: -20px;
right: -20px;
bottom: -20px;
background: radial-gradient(circle at var(--mouse-x, 50%) var(--mouse-y, 50%), rgba(76, 175, 80, 0.05) 0%, transparent 50%);
border-radius: 30px;
pointer-events: none;
z-index: -1;
transition: opacity 0.3s ease;
}
}
/* 季节性主题变化可通过JavaScript控制 */
.theme-spring body {
background: linear-gradient(135deg, #e8f5e8 0%, #f0f9f0 25%, #e1f5e1 50%, #f8fdf8 75%, #e8f5e8 100%);
}
.theme-summer body {
background: linear-gradient(135deg, #f0f9f0 0%, #e8f5e8 25%, #f8fdf8 50%, #e1f5e1 75%, #f0f9f0 100%);
}
.theme-autumn body {
background: linear-gradient(135deg, #f5f0e8 0%, #f9f5f0 25%, #fdf8f0 50%, #f5f0e8 75%, #f9f5f0 100%);
}
.theme-winter body {
background: linear-gradient(135deg, #f0f5f8 0%, #f5f9fc 25%, #f8fbfd 50%, #f0f5f8 75%, #f5f9fc 100%);
}
/* 性能优化GPU加速 */
body,
body::before,
body::after {
will-change: transform;
transform: translateZ(0);
}
/* 无障碍支持:为屏幕阅读器隐藏装饰元素 */
body::before,
body::after {
speak: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
/* 背景样式文件 */
/* 主背景渐变 */
body {
background: linear-gradient(135deg, #e8f5e8 0%, #f0f9f0 25%, #f8fdf8 50%, #e8f5e8 75%, #f0f9f0 100%);
background-size: 400% 400%;
animation: gradientShift 15s ease infinite;
position: relative;
overflow-x: hidden;
}
/* 背景渐变动画 */
@keyframes gradientShift {
0% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
}
/* 装饰性背景元素 */
body::before {
content: '';
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image:
radial-gradient(circle at 20% 80%, rgba(76, 175, 80, 0.1) 0%, transparent 50%),
radial-gradient(circle at 80% 20%, rgba(45, 90, 61, 0.08) 0%, transparent 50%),
radial-gradient(circle at 40% 40%, rgba(76, 175, 80, 0.05) 0%, transparent 50%);
pointer-events: none;
z-index: -2;
}
/* 浮动装饰圆点 */
body::after {
content: '';
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image:
radial-gradient(2px 2px at 20px 30px, rgba(76, 175, 80, 0.3), transparent),
radial-gradient(2px 2px at 40px 70px, rgba(45, 90, 61, 0.2), transparent),
radial-gradient(1px 1px at 90px 40px, rgba(76, 175, 80, 0.4), transparent),
radial-gradient(1px 1px at 130px 80px, rgba(45, 90, 61, 0.3), transparent),
radial-gradient(2px 2px at 160px 30px, rgba(76, 175, 80, 0.2), transparent);
background-repeat: repeat;
background-size: 200px 100px;
animation: floatDots 20s linear infinite;
pointer-events: none;
z-index: -1;
opacity: 0.6;
}
/* 圆点浮动动画 */
@keyframes floatDots {
0% {
transform: translateY(0px) translateX(0px);
}
33% {
transform: translateY(-10px) translateX(5px);
}
66% {
transform: translateY(5px) translateX(-5px);
}
100% {
transform: translateY(0px) translateX(0px);
}
}
/* 网格背景(可选,默认隐藏) */
.grid-background {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image:
linear-gradient(rgba(76, 175, 80, 0.03) 1px, transparent 1px),
linear-gradient(90deg, rgba(76, 175, 80, 0.03) 1px, transparent 1px);
background-size: 50px 50px;
pointer-events: none;
z-index: -3;
opacity: 0;
transition: opacity 0.3s ease;
}
.grid-background.active {
opacity: 1;
}
/* 响应式背景调整 */
@media (max-width: 768px) {
body::after {
background-size: 150px 75px;
animation-duration: 25s;
}
body::before {
background-image:
radial-gradient(circle at 30% 70%, rgba(76, 175, 80, 0.08) 0%, transparent 50%),
radial-gradient(circle at 70% 30%, rgba(45, 90, 61, 0.06) 0%, transparent 50%);
}
}
@media (max-width: 480px) {
body {
animation-duration: 20s;
}
body::after {
background-size: 100px 50px;
opacity: 0.4;
}
}
/* 高对比度模式下的背景调整 */
@media (prefers-contrast: high) {
body {
background: #f8fdf8;
animation: none;
}
body::before,
body::after {
display: none;
}
}
/* 减少动画模式下的背景调整 */
@media (prefers-reduced-motion: reduce) {
body {
animation: none;
background: linear-gradient(135deg, #e8f5e8 0%, #f0f9f0 50%, #f8fdf8 100%);
}
body::after {
animation: none;
}
@keyframes gradientShift {
0%, 100% {
background-position: 0% 50%;
}
}
@keyframes floatDots {
0%, 100% {
transform: translateY(0px) translateX(0px);
}
}
}
/* 深色模式支持 */
@media (prefers-color-scheme: dark) {
body {
background: linear-gradient(135deg, #1a2e1a 0%, #2d4a2d 25%, #1f3a1f 50%, #1a2e1a 75%, #2d4a2d 100%);
}
body::before {
background-image:
radial-gradient(circle at 20% 80%, rgba(76, 175, 80, 0.15) 0%, transparent 50%),
radial-gradient(circle at 80% 20%, rgba(144, 238, 144, 0.1) 0%, transparent 50%),
radial-gradient(circle at 40% 40%, rgba(76, 175, 80, 0.08) 0%, transparent 50%);
}
body::after {
background-image:
radial-gradient(2px 2px at 20px 30px, rgba(144, 238, 144, 0.4), transparent),
radial-gradient(2px 2px at 40px 70px, rgba(76, 175, 80, 0.3), transparent),
radial-gradient(1px 1px at 90px 40px, rgba(144, 238, 144, 0.5), transparent),
radial-gradient(1px 1px at 130px 80px, rgba(76, 175, 80, 0.4), transparent),
radial-gradient(2px 2px at 160px 30px, rgba(144, 238, 144, 0.3), transparent);
}
}
/* 打印样式 */
@media print {
body {
background: white !important;
animation: none !important;
}
body::before,
body::after {
display: none !important;
}
}
/* 特殊效果:鼠标悬停时的背景变化 */
@media (hover: hover) {
.container:hover {
position: relative;
}
.container:hover::before {
content: '';
position: absolute;
top: -20px;
left: -20px;
right: -20px;
bottom: -20px;
background: radial-gradient(circle at var(--mouse-x, 50%) var(--mouse-y, 50%), rgba(76, 175, 80, 0.05) 0%, transparent 50%);
border-radius: 30px;
pointer-events: none;
z-index: -1;
transition: opacity 0.3s ease;
}
}
/* 季节性主题变化可通过JavaScript控制 */
.theme-spring body {
background: linear-gradient(135deg, #e8f5e8 0%, #f0f9f0 25%, #e1f5e1 50%, #f8fdf8 75%, #e8f5e8 100%);
}
.theme-summer body {
background: linear-gradient(135deg, #f0f9f0 0%, #e8f5e8 25%, #f8fdf8 50%, #e1f5e1 75%, #f0f9f0 100%);
}
.theme-autumn body {
background: linear-gradient(135deg, #f5f0e8 0%, #f9f5f0 25%, #fdf8f0 50%, #f5f0e8 75%, #f9f5f0 100%);
}
.theme-winter body {
background: linear-gradient(135deg, #f0f5f8 0%, #f5f9fc 25%, #f8fbfd 50%, #f0f5f8 75%, #f5f9fc 100%);
}
/* 性能优化GPU加速 */
body,
body::before,
body::after {
will-change: transform;
transform: translateZ(0);
}
/* 无障碍支持:为屏幕阅读器隐藏装饰元素 */
body::before,
body::after {
speak: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}

View File

@@ -1,146 +1,146 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>随机密码生成器</title>
<link rel="stylesheet" href="css/style.css">
<link rel="stylesheet" href="css/background.css">
</head>
<body>
<div class="container">
<header class="header">
<h1>🔐 随机密码生成器</h1>
<p class="subtitle">生成安全可靠的随机密码</p>
</header>
<main class="main-content">
<div class="form-container">
<form id="passwordForm" class="password-form">
<div class="form-group">
<label for="length">密码长度</label>
<div class="length-control">
<input type="range" id="length" name="length" min="4" max="128" value="16" class="length-slider">
<span id="lengthDisplay" class="length-display">16</span>
</div>
</div>
<div class="form-group">
<label class="section-title">字符类型</label>
<div class="checkbox-group">
<div class="checkbox-item">
<input type="checkbox" id="numbers" name="numbers" checked>
<label for="numbers">包含数字 (0-9)</label>
</div>
<div class="checkbox-item">
<input type="checkbox" id="uppercase" name="uppercase" checked>
<label for="uppercase">包含大写字母 (A-Z)</label>
</div>
<div class="checkbox-item">
<input type="checkbox" id="lowercase" name="lowercase" checked>
<label for="lowercase">包含小写字母 (a-z)</label>
</div>
<div class="checkbox-item">
<input type="checkbox" id="symbols" name="symbols">
<label for="symbols">包含特殊字符 (!@#$%^&*)</label>
</div>
</div>
</div>
<div class="form-group">
<label class="section-title">高级选项</label>
<div class="checkbox-group">
<div class="checkbox-item">
<input type="checkbox" id="excludeSimilar" name="excludeSimilar" checked>
<label for="excludeSimilar">排除相似字符 (0,O,l,1,I)</label>
</div>
<div class="checkbox-item">
<input type="checkbox" id="excludeAmbiguous" name="excludeAmbiguous" checked>
<label for="excludeAmbiguous">排除模糊字符</label>
</div>
</div>
</div>
<button type="submit" class="generate-btn" id="generateBtn">
<span class="btn-text">生成密码</span>
<span class="btn-loading" style="display: none;">生成中...</span>
</button>
</form>
</div>
<div class="result-container" id="resultContainer" style="display: none;">
<div class="result-header">
<h3>生成的密码</h3>
<button class="copy-btn" id="copyBtn" title="复制密码">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
</svg>
</button>
</div>
<div class="password-display">
<input type="text" id="passwordResult" class="password-input" readonly>
</div>
<div class="password-info">
<div class="info-item">
<span class="info-label">长度:</span>
<span id="infoLength" class="info-value">-</span>
</div>
<div class="info-item">
<span class="info-label">强度:</span>
<span id="infoStrength" class="info-value strength">-</span>
</div>
<div class="info-item">
<span class="info-label">熵值:</span>
<span id="infoEntropy" class="info-value">-</span>
</div>
<div class="info-item">
<span class="info-label">数字:</span>
<span id="infoNumbers" class="info-value">-</span>
</div>
<div class="info-item">
<span class="info-label">大写:</span>
<span id="infoUppercase" class="info-value">-</span>
</div>
<div class="info-item">
<span class="info-label">小写:</span>
<span id="infoLowercase" class="info-value">-</span>
</div>
<div class="info-item">
<span class="info-label">符号:</span>
<span id="infoSymbols" class="info-value">-</span>
</div>
<div class="info-item full-width">
<span class="info-label">破解时间:</span>
<span id="infoCrackTime" class="info-value">-</span>
</div>
</div>
<div class="character-sets" id="characterSets">
<h4>使用的字符集</h4>
<div class="sets-list" id="setsList"></div>
</div>
</div>
<div class="error-container" id="errorContainer" style="display: none;">
<div class="error-icon">⚠️</div>
<h3>生成失败</h3>
<p id="errorMessage">请检查网络连接后重试</p>
<button class="retry-btn" id="retryBtn">重新生成</button>
</div>
</main>
<footer class="footer">
<p>安全密码生成工具</p>
</footer>
</div>
<div class="toast" id="toast" style="display: none;">
<span id="toastMessage">密码已复制到剪贴板</span>
</div>
<script src="js/script.js"></script>
</body>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>随机密码生成器</title>
<link rel="stylesheet" href="css/style.css">
<link rel="stylesheet" href="css/background.css">
</head>
<body>
<div class="container">
<header class="header">
<h1>🔐 随机密码生成器</h1>
<p class="subtitle">生成安全可靠的随机密码</p>
</header>
<main class="main-content">
<div class="form-container">
<form id="passwordForm" class="password-form">
<div class="form-group">
<label for="length">密码长度</label>
<div class="length-control">
<input type="range" id="length" name="length" min="4" max="128" value="16" class="length-slider">
<span id="lengthDisplay" class="length-display">16</span>
</div>
</div>
<div class="form-group">
<label class="section-title">字符类型</label>
<div class="checkbox-group">
<div class="checkbox-item">
<input type="checkbox" id="numbers" name="numbers" checked>
<label for="numbers">包含数字 (0-9)</label>
</div>
<div class="checkbox-item">
<input type="checkbox" id="uppercase" name="uppercase" checked>
<label for="uppercase">包含大写字母 (A-Z)</label>
</div>
<div class="checkbox-item">
<input type="checkbox" id="lowercase" name="lowercase" checked>
<label for="lowercase">包含小写字母 (a-z)</label>
</div>
<div class="checkbox-item">
<input type="checkbox" id="symbols" name="symbols">
<label for="symbols">包含特殊字符 (!@#$%^&*)</label>
</div>
</div>
</div>
<div class="form-group">
<label class="section-title">高级选项</label>
<div class="checkbox-group">
<div class="checkbox-item">
<input type="checkbox" id="excludeSimilar" name="excludeSimilar" checked>
<label for="excludeSimilar">排除相似字符 (0,O,l,1,I)</label>
</div>
<div class="checkbox-item">
<input type="checkbox" id="excludeAmbiguous" name="excludeAmbiguous" checked>
<label for="excludeAmbiguous">排除模糊字符</label>
</div>
</div>
</div>
<button type="submit" class="generate-btn" id="generateBtn">
<span class="btn-text">生成密码</span>
<span class="btn-loading" style="display: none;">生成中...</span>
</button>
</form>
</div>
<div class="result-container" id="resultContainer" style="display: none;">
<div class="result-header">
<h3>生成的密码</h3>
<button class="copy-btn" id="copyBtn" title="复制密码">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
</svg>
</button>
</div>
<div class="password-display">
<input type="text" id="passwordResult" class="password-input" readonly>
</div>
<div class="password-info">
<div class="info-item">
<span class="info-label">长度:</span>
<span id="infoLength" class="info-value">-</span>
</div>
<div class="info-item">
<span class="info-label">强度:</span>
<span id="infoStrength" class="info-value strength">-</span>
</div>
<div class="info-item">
<span class="info-label">熵值:</span>
<span id="infoEntropy" class="info-value">-</span>
</div>
<div class="info-item">
<span class="info-label">数字:</span>
<span id="infoNumbers" class="info-value">-</span>
</div>
<div class="info-item">
<span class="info-label">大写:</span>
<span id="infoUppercase" class="info-value">-</span>
</div>
<div class="info-item">
<span class="info-label">小写:</span>
<span id="infoLowercase" class="info-value">-</span>
</div>
<div class="info-item">
<span class="info-label">符号:</span>
<span id="infoSymbols" class="info-value">-</span>
</div>
<div class="info-item full-width">
<span class="info-label">破解时间:</span>
<span id="infoCrackTime" class="info-value">-</span>
</div>
</div>
<div class="character-sets" id="characterSets">
<h4>使用的字符集</h4>
<div class="sets-list" id="setsList"></div>
</div>
</div>
<div class="error-container" id="errorContainer" style="display: none;">
<div class="error-icon">⚠️</div>
<h3>生成失败</h3>
<p id="errorMessage">请检查网络连接后重试</p>
<button class="retry-btn" id="retryBtn">重新生成</button>
</div>
</main>
<footer class="footer">
<p>安全密码生成工具</p>
</footer>
</div>
<div class="toast" id="toast" style="display: none;">
<span id="toastMessage">密码已复制到剪贴板</span>
</div>
<script src="js/script.js"></script>
</body>
</html>

View File

@@ -1,412 +1,412 @@
class PasswordGenerator {
constructor() {
this.apiUrl = 'https://60s.api.shumengya.top/v2/password';
this.loadStartTime = 0;
this.init();
}
init() {
this.bindEvents();
this.updateLengthDisplay();
this.preloadResources();
}
preloadResources() {
// 预连接API服务器
const link = document.createElement('link');
link.rel = 'preconnect';
link.href = 'https://60s.api.shumengya.top';
document.head.appendChild(link);
}
bindEvents() {
// 长度滑块事件
const lengthSlider = document.getElementById('length');
lengthSlider.addEventListener('input', () => this.updateLengthDisplay());
// 生成按钮事件
const generateBtn = document.getElementById('generateBtn');
generateBtn.addEventListener('click', () => this.generatePassword());
// 复制按钮事件
const copyBtn = document.getElementById('copyBtn');
copyBtn.addEventListener('click', () => this.copyPassword());
// 重试按钮事件
const retryBtn = document.getElementById('retryBtn');
retryBtn.addEventListener('click', () => this.generatePassword());
// 复选框变化事件
const checkboxes = document.querySelectorAll('input[type="checkbox"]');
checkboxes.forEach(checkbox => {
checkbox.addEventListener('change', () => this.validateForm());
});
// 键盘快捷键
document.addEventListener('keydown', (e) => {
if (e.ctrlKey && e.key === 'Enter') {
e.preventDefault();
this.generatePassword();
}
if (e.ctrlKey && e.key === 'c' && document.activeElement.id === 'passwordResult') {
this.copyPassword();
}
});
}
updateLengthDisplay() {
const lengthSlider = document.getElementById('length');
const lengthDisplay = document.getElementById('lengthDisplay');
lengthDisplay.textContent = lengthSlider.value;
}
validateForm() {
const checkboxes = document.querySelectorAll('input[type="checkbox"]:checked');
const generateBtn = document.getElementById('generateBtn');
// 至少需要选择一种字符类型
const hasCharacterType = Array.from(checkboxes).some(cb =>
['numbers', 'uppercase', 'lowercase', 'symbols'].includes(cb.id)
);
generateBtn.disabled = !hasCharacterType;
if (!hasCharacterType) {
this.showToast('请至少选择一种字符类型', 'warning');
}
}
async generatePassword() {
this.loadStartTime = Date.now();
try {
this.showLoading(true);
this.hideError();
const params = this.getFormParams();
const password = await this.callAPI(params);
if (password) {
this.displayPassword(password, params);
this.showToast('密码生成成功!', 'success');
const loadTime = Date.now() - this.loadStartTime;
console.log(`密码生成完成,耗时: ${loadTime}ms`);
}
} catch (error) {
console.error('生成密码失败:', error);
this.showError(error.message || '生成密码时发生错误,请重试');
} finally {
this.showLoading(false);
}
}
getFormParams() {
const length = document.getElementById('length').value;
const numbers = document.getElementById('numbers').checked;
const uppercase = document.getElementById('uppercase').checked;
const lowercase = document.getElementById('lowercase').checked;
const symbols = document.getElementById('symbols').checked;
const excludeSimilar = document.getElementById('excludeSimilar').checked;
const excludeAmbiguous = document.getElementById('excludeAmbiguous').checked;
return {
length: parseInt(length),
numbers: numbers ? 'true' : 'false',
uppercase: uppercase ? 'true' : 'false',
lowercase: lowercase ? 'true' : 'false',
symbols: symbols ? 'true' : 'false',
exclude_similar: excludeSimilar ? 'true' : 'false',
exclude_ambiguous: excludeAmbiguous ? 'true' : 'false',
encoding: 'json'
};
}
async callAPI(params) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 5000);
try {
const url = new URL(this.apiUrl);
Object.keys(params).forEach(key => {
if (params[key] !== undefined && params[key] !== null) {
url.searchParams.append(key, params[key]);
}
});
const response = await fetch(url.toString(), {
method: 'GET',
signal: controller.signal,
headers: {
'Accept': 'application/json',
'User-Agent': 'PasswordGenerator/1.0'
}
});
clearTimeout(timeoutId);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
if (data.code === 200 && data.data && data.data.password) {
return data.data.password;
} else {
throw new Error(data.message || '服务器返回了无效的密码数据');
}
} catch (error) {
clearTimeout(timeoutId);
if (error.name === 'AbortError') {
throw new Error('请求超时,请检查网络连接后重试');
}
if (error.message.includes('Failed to fetch')) {
throw new Error('网络连接失败,请检查网络后重试');
}
throw error;
}
}
displayPassword(password, params) {
// 显示结果容器
const resultContainer = document.getElementById('resultContainer');
const errorContainer = document.getElementById('errorContainer');
resultContainer.style.display = 'block';
errorContainer.style.display = 'none';
// 设置密码
const passwordInput = document.getElementById('passwordResult');
passwordInput.value = password;
// 计算并显示密码信息
this.updatePasswordInfo(password, params);
// 滚动到结果区域
resultContainer.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
updatePasswordInfo(password, params) {
// 基本信息
document.getElementById('infoLength').textContent = password.length;
document.getElementById('infoEntropy').textContent = this.calculateEntropy(password).toFixed(1);
// 密码强度
const strength = this.calculateStrength(password);
const strengthElement = document.getElementById('infoStrength');
strengthElement.textContent = strength.text;
strengthElement.className = `info-value strength ${strength.class}`;
// 字符类型统计
const stats = this.analyzeCharacters(password);
document.getElementById('infoNumbers').textContent = stats.numbers;
document.getElementById('infoUppercase').textContent = stats.uppercase;
document.getElementById('infoLowercase').textContent = stats.lowercase;
document.getElementById('infoSymbols').textContent = stats.symbols;
// 使用的字符集
this.updateCharacterSets(params);
// 破解时间估算
document.getElementById('infoCrackTime').textContent = this.estimateCrackTime(password);
}
calculateEntropy(password) {
const charset = this.getCharsetSize(password);
return Math.log2(Math.pow(charset, password.length));
}
getCharsetSize(password) {
let size = 0;
if (/[0-9]/.test(password)) size += 10;
if (/[a-z]/.test(password)) size += 26;
if (/[A-Z]/.test(password)) size += 26;
if (/[^a-zA-Z0-9]/.test(password)) size += 32;
return size;
}
calculateStrength(password) {
const entropy = this.calculateEntropy(password);
if (entropy < 30) {
return { text: '弱', class: 'weak' };
} else if (entropy < 50) {
return { text: '中等', class: 'medium' };
} else if (entropy < 70) {
return { text: '强', class: 'strong' };
} else {
return { text: '非常强', class: 'very-strong' };
}
}
analyzeCharacters(password) {
return {
numbers: (password.match(/[0-9]/g) || []).length,
uppercase: (password.match(/[A-Z]/g) || []).length,
lowercase: (password.match(/[a-z]/g) || []).length,
symbols: (password.match(/[^a-zA-Z0-9]/g) || []).length
};
}
updateCharacterSets(params) {
const setsList = document.getElementById('setsList');
const sets = [];
if (params.numbers === 'true') sets.push('数字 (0-9)');
if (params.uppercase === 'true') sets.push('大写字母 (A-Z)');
if (params.lowercase === 'true') sets.push('小写字母 (a-z)');
if (params.symbols === 'true') sets.push('特殊字符 (!@#$...)');
setsList.innerHTML = sets.map(set => `<span class="set-item">${set}</span>`).join('');
}
estimateCrackTime(password) {
const charset = this.getCharsetSize(password);
const combinations = Math.pow(charset, password.length);
const guessesPerSecond = 1e9; // 假设每秒10亿次尝试
const secondsToCrack = combinations / (2 * guessesPerSecond);
if (secondsToCrack < 60) {
return '不到1分钟';
} else if (secondsToCrack < 3600) {
return `${Math.ceil(secondsToCrack / 60)}分钟`;
} else if (secondsToCrack < 86400) {
return `${Math.ceil(secondsToCrack / 3600)}小时`;
} else if (secondsToCrack < 31536000) {
return `${Math.ceil(secondsToCrack / 86400)}`;
} else if (secondsToCrack < 31536000000) {
return `${Math.ceil(secondsToCrack / 31536000)}`;
} else {
return '数千年以上';
}
}
async copyPassword() {
const passwordInput = document.getElementById('passwordResult');
try {
if (navigator.clipboard && window.isSecureContext) {
await navigator.clipboard.writeText(passwordInput.value);
} else {
// 降级方案
passwordInput.select();
passwordInput.setSelectionRange(0, 99999);
document.execCommand('copy');
}
this.showToast('密码已复制到剪贴板!', 'success');
// 复制按钮反馈
const copyBtn = document.getElementById('copyBtn');
const originalText = copyBtn.innerHTML;
copyBtn.innerHTML = '✓ 已复制';
copyBtn.style.background = '#2e7d32';
setTimeout(() => {
copyBtn.innerHTML = originalText;
copyBtn.style.background = '';
}, 2000);
} catch (error) {
console.error('复制失败:', error);
this.showToast('复制失败,请手动选择密码', 'error');
}
}
showLoading(show) {
const generateBtn = document.getElementById('generateBtn');
if (show) {
generateBtn.disabled = true;
generateBtn.innerHTML = '<span style="display: inline-block; animation: spin 1s linear infinite;">⟳</span> 生成中...';
} else {
generateBtn.disabled = false;
generateBtn.innerHTML = '🔐 生成密码';
}
}
showError(message) {
const errorContainer = document.getElementById('errorContainer');
const resultContainer = document.getElementById('resultContainer');
const errorMessage = document.getElementById('errorMessage');
errorMessage.textContent = message;
errorContainer.style.display = 'block';
resultContainer.style.display = 'none';
errorContainer.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
hideError() {
const errorContainer = document.getElementById('errorContainer');
errorContainer.style.display = 'none';
}
showToast(message, type = 'info') {
// 移除现有的toast
const existingToast = document.querySelector('.toast');
if (existingToast) {
existingToast.remove();
}
const toast = document.createElement('div');
toast.className = 'toast';
toast.textContent = message;
// 根据类型设置颜色
const colors = {
success: '#4caf50',
error: '#f44336',
warning: '#ff9800',
info: '#2196f3'
};
toast.style.background = colors[type] || colors.info;
document.body.appendChild(toast);
// 3秒后自动移除
setTimeout(() => {
if (toast.parentNode) {
toast.remove();
}
}, 3000);
}
}
// 添加旋转动画样式
const style = document.createElement('style');
style.textContent = `
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
`;
document.head.appendChild(style);
// 页面加载完成后初始化
document.addEventListener('DOMContentLoaded', () => {
new PasswordGenerator();
});
// 页面可见性变化时的处理
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'visible') {
// 页面重新可见时,可以进行一些刷新操作
console.log('页面重新可见');
}
});
// 错误处理
window.addEventListener('error', (event) => {
console.error('全局错误:', event.error);
});
window.addEventListener('unhandledrejection', (event) => {
console.error('未处理的Promise拒绝:', event.reason);
event.preventDefault();
class PasswordGenerator {
constructor() {
this.apiUrl = 'https://60s.api.shumengya.top/v2/password';
this.loadStartTime = 0;
this.init();
}
init() {
this.bindEvents();
this.updateLengthDisplay();
this.preloadResources();
}
preloadResources() {
// 预连接API服务器
const link = document.createElement('link');
link.rel = 'preconnect';
link.href = 'https://60s.api.shumengya.top';
document.head.appendChild(link);
}
bindEvents() {
// 长度滑块事件
const lengthSlider = document.getElementById('length');
lengthSlider.addEventListener('input', () => this.updateLengthDisplay());
// 生成按钮事件
const generateBtn = document.getElementById('generateBtn');
generateBtn.addEventListener('click', () => this.generatePassword());
// 复制按钮事件
const copyBtn = document.getElementById('copyBtn');
copyBtn.addEventListener('click', () => this.copyPassword());
// 重试按钮事件
const retryBtn = document.getElementById('retryBtn');
retryBtn.addEventListener('click', () => this.generatePassword());
// 复选框变化事件
const checkboxes = document.querySelectorAll('input[type="checkbox"]');
checkboxes.forEach(checkbox => {
checkbox.addEventListener('change', () => this.validateForm());
});
// 键盘快捷键
document.addEventListener('keydown', (e) => {
if (e.ctrlKey && e.key === 'Enter') {
e.preventDefault();
this.generatePassword();
}
if (e.ctrlKey && e.key === 'c' && document.activeElement.id === 'passwordResult') {
this.copyPassword();
}
});
}
updateLengthDisplay() {
const lengthSlider = document.getElementById('length');
const lengthDisplay = document.getElementById('lengthDisplay');
lengthDisplay.textContent = lengthSlider.value;
}
validateForm() {
const checkboxes = document.querySelectorAll('input[type="checkbox"]:checked');
const generateBtn = document.getElementById('generateBtn');
// 至少需要选择一种字符类型
const hasCharacterType = Array.from(checkboxes).some(cb =>
['numbers', 'uppercase', 'lowercase', 'symbols'].includes(cb.id)
);
generateBtn.disabled = !hasCharacterType;
if (!hasCharacterType) {
this.showToast('请至少选择一种字符类型', 'warning');
}
}
async generatePassword() {
this.loadStartTime = Date.now();
try {
this.showLoading(true);
this.hideError();
const params = this.getFormParams();
const password = await this.callAPI(params);
if (password) {
this.displayPassword(password, params);
this.showToast('密码生成成功!', 'success');
const loadTime = Date.now() - this.loadStartTime;
console.log(`密码生成完成,耗时: ${loadTime}ms`);
}
} catch (error) {
console.error('生成密码失败:', error);
this.showError(error.message || '生成密码时发生错误,请重试');
} finally {
this.showLoading(false);
}
}
getFormParams() {
const length = document.getElementById('length').value;
const numbers = document.getElementById('numbers').checked;
const uppercase = document.getElementById('uppercase').checked;
const lowercase = document.getElementById('lowercase').checked;
const symbols = document.getElementById('symbols').checked;
const excludeSimilar = document.getElementById('excludeSimilar').checked;
const excludeAmbiguous = document.getElementById('excludeAmbiguous').checked;
return {
length: parseInt(length),
numbers: numbers ? 'true' : 'false',
uppercase: uppercase ? 'true' : 'false',
lowercase: lowercase ? 'true' : 'false',
symbols: symbols ? 'true' : 'false',
exclude_similar: excludeSimilar ? 'true' : 'false',
exclude_ambiguous: excludeAmbiguous ? 'true' : 'false',
encoding: 'json'
};
}
async callAPI(params) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 5000);
try {
const url = new URL(this.apiUrl);
Object.keys(params).forEach(key => {
if (params[key] !== undefined && params[key] !== null) {
url.searchParams.append(key, params[key]);
}
});
const response = await fetch(url.toString(), {
method: 'GET',
signal: controller.signal,
headers: {
'Accept': 'application/json',
'User-Agent': 'PasswordGenerator/1.0'
}
});
clearTimeout(timeoutId);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
if (data.code === 200 && data.data && data.data.password) {
return data.data.password;
} else {
throw new Error(data.message || '服务器返回了无效的密码数据');
}
} catch (error) {
clearTimeout(timeoutId);
if (error.name === 'AbortError') {
throw new Error('请求超时,请检查网络连接后重试');
}
if (error.message.includes('Failed to fetch')) {
throw new Error('网络连接失败,请检查网络后重试');
}
throw error;
}
}
displayPassword(password, params) {
// 显示结果容器
const resultContainer = document.getElementById('resultContainer');
const errorContainer = document.getElementById('errorContainer');
resultContainer.style.display = 'block';
errorContainer.style.display = 'none';
// 设置密码
const passwordInput = document.getElementById('passwordResult');
passwordInput.value = password;
// 计算并显示密码信息
this.updatePasswordInfo(password, params);
// 滚动到结果区域
resultContainer.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
updatePasswordInfo(password, params) {
// 基本信息
document.getElementById('infoLength').textContent = password.length;
document.getElementById('infoEntropy').textContent = this.calculateEntropy(password).toFixed(1);
// 密码强度
const strength = this.calculateStrength(password);
const strengthElement = document.getElementById('infoStrength');
strengthElement.textContent = strength.text;
strengthElement.className = `info-value strength ${strength.class}`;
// 字符类型统计
const stats = this.analyzeCharacters(password);
document.getElementById('infoNumbers').textContent = stats.numbers;
document.getElementById('infoUppercase').textContent = stats.uppercase;
document.getElementById('infoLowercase').textContent = stats.lowercase;
document.getElementById('infoSymbols').textContent = stats.symbols;
// 使用的字符集
this.updateCharacterSets(params);
// 破解时间估算
document.getElementById('infoCrackTime').textContent = this.estimateCrackTime(password);
}
calculateEntropy(password) {
const charset = this.getCharsetSize(password);
return Math.log2(Math.pow(charset, password.length));
}
getCharsetSize(password) {
let size = 0;
if (/[0-9]/.test(password)) size += 10;
if (/[a-z]/.test(password)) size += 26;
if (/[A-Z]/.test(password)) size += 26;
if (/[^a-zA-Z0-9]/.test(password)) size += 32;
return size;
}
calculateStrength(password) {
const entropy = this.calculateEntropy(password);
if (entropy < 30) {
return { text: '弱', class: 'weak' };
} else if (entropy < 50) {
return { text: '中等', class: 'medium' };
} else if (entropy < 70) {
return { text: '强', class: 'strong' };
} else {
return { text: '非常强', class: 'very-strong' };
}
}
analyzeCharacters(password) {
return {
numbers: (password.match(/[0-9]/g) || []).length,
uppercase: (password.match(/[A-Z]/g) || []).length,
lowercase: (password.match(/[a-z]/g) || []).length,
symbols: (password.match(/[^a-zA-Z0-9]/g) || []).length
};
}
updateCharacterSets(params) {
const setsList = document.getElementById('setsList');
const sets = [];
if (params.numbers === 'true') sets.push('数字 (0-9)');
if (params.uppercase === 'true') sets.push('大写字母 (A-Z)');
if (params.lowercase === 'true') sets.push('小写字母 (a-z)');
if (params.symbols === 'true') sets.push('特殊字符 (!@#$...)');
setsList.innerHTML = sets.map(set => `<span class="set-item">${set}</span>`).join('');
}
estimateCrackTime(password) {
const charset = this.getCharsetSize(password);
const combinations = Math.pow(charset, password.length);
const guessesPerSecond = 1e9; // 假设每秒10亿次尝试
const secondsToCrack = combinations / (2 * guessesPerSecond);
if (secondsToCrack < 60) {
return '不到1分钟';
} else if (secondsToCrack < 3600) {
return `${Math.ceil(secondsToCrack / 60)}分钟`;
} else if (secondsToCrack < 86400) {
return `${Math.ceil(secondsToCrack / 3600)}小时`;
} else if (secondsToCrack < 31536000) {
return `${Math.ceil(secondsToCrack / 86400)}`;
} else if (secondsToCrack < 31536000000) {
return `${Math.ceil(secondsToCrack / 31536000)}`;
} else {
return '数千年以上';
}
}
async copyPassword() {
const passwordInput = document.getElementById('passwordResult');
try {
if (navigator.clipboard && window.isSecureContext) {
await navigator.clipboard.writeText(passwordInput.value);
} else {
// 降级方案
passwordInput.select();
passwordInput.setSelectionRange(0, 99999);
document.execCommand('copy');
}
this.showToast('密码已复制到剪贴板!', 'success');
// 复制按钮反馈
const copyBtn = document.getElementById('copyBtn');
const originalText = copyBtn.innerHTML;
copyBtn.innerHTML = '✓ 已复制';
copyBtn.style.background = '#2e7d32';
setTimeout(() => {
copyBtn.innerHTML = originalText;
copyBtn.style.background = '';
}, 2000);
} catch (error) {
console.error('复制失败:', error);
this.showToast('复制失败,请手动选择密码', 'error');
}
}
showLoading(show) {
const generateBtn = document.getElementById('generateBtn');
if (show) {
generateBtn.disabled = true;
generateBtn.innerHTML = '<span style="display: inline-block; animation: spin 1s linear infinite;">⟳</span> 生成中...';
} else {
generateBtn.disabled = false;
generateBtn.innerHTML = '🔐 生成密码';
}
}
showError(message) {
const errorContainer = document.getElementById('errorContainer');
const resultContainer = document.getElementById('resultContainer');
const errorMessage = document.getElementById('errorMessage');
errorMessage.textContent = message;
errorContainer.style.display = 'block';
resultContainer.style.display = 'none';
errorContainer.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
hideError() {
const errorContainer = document.getElementById('errorContainer');
errorContainer.style.display = 'none';
}
showToast(message, type = 'info') {
// 移除现有的toast
const existingToast = document.querySelector('.toast');
if (existingToast) {
existingToast.remove();
}
const toast = document.createElement('div');
toast.className = 'toast';
toast.textContent = message;
// 根据类型设置颜色
const colors = {
success: '#4caf50',
error: '#f44336',
warning: '#ff9800',
info: '#2196f3'
};
toast.style.background = colors[type] || colors.info;
document.body.appendChild(toast);
// 3秒后自动移除
setTimeout(() => {
if (toast.parentNode) {
toast.remove();
}
}, 3000);
}
}
// 添加旋转动画样式
const style = document.createElement('style');
style.textContent = `
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
`;
document.head.appendChild(style);
// 页面加载完成后初始化
document.addEventListener('DOMContentLoaded', () => {
new PasswordGenerator();
});
// 页面可见性变化时的处理
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'visible') {
// 页面重新可见时,可以进行一些刷新操作
console.log('页面重新可见');
}
});
// 错误处理
window.addEventListener('error', (event) => {
console.error('全局错误:', event.error);
});
window.addEventListener('unhandledrejection', (event) => {
console.error('未处理的Promise拒绝:', event.reason);
event.preventDefault();
});

View File

@@ -1,32 +1,32 @@
{
"code": 200,
"message": "获取成功。数据来自官方/权威源头,以确保稳定与实时。开源地址 https://github.com/vikiboss/60s反馈群 595941841",
"data": {
"password": "8mr2M7dZ6E3saj3F",
"length": 16,
"config": {
"include_numbers": true,
"include_symbols": false,
"include_lowercase": true,
"include_uppercase": true,
"exclude_similar": true,
"exclude_ambiguous": true
},
"character_sets": {
"lowercase": "abcdefghjkmnpqrstuvwxyz",
"uppercase": "ABCDEFGHIJKMNPQRSTUVWXYZ",
"numbers": "23456789",
"symbols": "",
"used_sets": [
"lowercase",
"uppercase",
"numbers"
]
},
"generation_info": {
"entropy": 92.5,
"strength": "极强",
"time_to_crack": "数百万年"
}
}
{
"code": 200,
"message": "获取成功。数据来自官方/权威源头,以确保稳定与实时。开源地址 https://github.com/vikiboss/60s反馈群 595941841",
"data": {
"password": "8mr2M7dZ6E3saj3F",
"length": 16,
"config": {
"include_numbers": true,
"include_symbols": false,
"include_lowercase": true,
"include_uppercase": true,
"exclude_similar": true,
"exclude_ambiguous": true
},
"character_sets": {
"lowercase": "abcdefghjkmnpqrstuvwxyz",
"uppercase": "ABCDEFGHIJKMNPQRSTUVWXYZ",
"numbers": "23456789",
"symbols": "",
"used_sets": [
"lowercase",
"uppercase",
"numbers"
]
},
"generation_info": {
"entropy": 92.5,
"strength": "极强",
"time_to_crack": "数百万年"
}
}
}

View File

@@ -1,215 +1,215 @@
/* 背景样式文件 - 独立管理背景相关样式 */
/* 主体背景 */
body {
background: linear-gradient(135deg, #e8f5e8 0%, #f0fff0 50%, #e8f5e8 100%);
background-attachment: fixed;
background-size: 400% 400%;
animation: gradientShift 15s ease infinite;
}
/* 背景动画 */
@keyframes gradientShift {
0% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
}
/* 容器背景装饰 */
.container::before {
content: '';
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image:
radial-gradient(circle at 20% 80%, rgba(144, 205, 144, 0.1) 0%, transparent 50%),
radial-gradient(circle at 80% 20%, rgba(45, 90, 39, 0.05) 0%, transparent 50%),
radial-gradient(circle at 40% 40%, rgba(144, 205, 144, 0.08) 0%, transparent 50%);
pointer-events: none;
z-index: -1;
}
/* 输入区域背景 */
.input-section {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
}
/* 结果区域背景 */
.result-section {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
}
/* 格式组背景 */
.format-group {
background: rgba(248, 255, 248, 0.8);
backdrop-filter: blur(5px);
-webkit-backdrop-filter: blur(5px);
}
/* 属性项背景 */
.property-item {
background: rgba(248, 255, 248, 0.8);
backdrop-filter: blur(5px);
-webkit-backdrop-filter: blur(5px);
}
/* 调色板项背景 */
.palette-item {
background: rgba(248, 255, 248, 0.8);
backdrop-filter: blur(5px);
-webkit-backdrop-filter: blur(5px);
}
/* 无障碍项背景 */
.accessibility-item {
background: rgba(248, 255, 248, 0.8);
backdrop-filter: blur(5px);
-webkit-backdrop-filter: blur(5px);
}
/* 颜色预览背景 */
.color-preview {
background: rgba(248, 255, 248, 0.6);
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
}
/* 输入框背景 */
.input-group input,
.input-group select {
background: rgba(248, 255, 248, 0.9);
backdrop-filter: blur(5px);
-webkit-backdrop-filter: blur(5px);
}
.input-group input:focus,
.input-group select:focus {
background: rgba(255, 255, 255, 0.95);
}
/* 格式组内部元素背景 */
.format-group p {
background: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(3px);
-webkit-backdrop-filter: blur(3px);
}
/* 手机端背景优化 */
@media (max-width: 767px) {
body {
background: linear-gradient(180deg, #e8f5e8 0%, #f0fff0 50%, #e8f5e8 100%);
background-attachment: scroll; /* 手机端使用scroll避免性能问题 */
}
.container::before {
background-image:
radial-gradient(circle at 30% 70%, rgba(144, 205, 144, 0.08) 0%, transparent 40%),
radial-gradient(circle at 70% 30%, rgba(45, 90, 39, 0.04) 0%, transparent 40%);
}
/* 减少手机端的模糊效果以提升性能 */
.input-section,
.result-section {
backdrop-filter: blur(5px);
-webkit-backdrop-filter: blur(5px);
}
.format-group,
.property-item,
.palette-item,
.accessibility-item {
backdrop-filter: blur(3px);
-webkit-backdrop-filter: blur(3px);
}
}
/* 平板端背景优化 */
@media (min-width: 768px) and (max-width: 1024px) {
.container::before {
background-image:
radial-gradient(circle at 25% 75%, rgba(144, 205, 144, 0.12) 0%, transparent 60%),
radial-gradient(circle at 75% 25%, rgba(45, 90, 39, 0.06) 0%, transparent 60%),
radial-gradient(circle at 50% 50%, rgba(144, 205, 144, 0.04) 0%, transparent 40%);
}
}
/* 电脑端背景优化 */
@media (min-width: 1025px) {
body {
background-size: 300% 300%;
animation-duration: 20s;
}
.container::before {
background-image:
radial-gradient(circle at 15% 85%, rgba(144, 205, 144, 0.15) 0%, transparent 70%),
radial-gradient(circle at 85% 15%, rgba(45, 90, 39, 0.08) 0%, transparent 70%),
radial-gradient(circle at 35% 35%, rgba(144, 205, 144, 0.1) 0%, transparent 50%),
radial-gradient(circle at 65% 65%, rgba(45, 90, 39, 0.05) 0%, transparent 50%);
}
/* 电脑端增强模糊效果 */
.input-section,
.result-section {
backdrop-filter: blur(15px);
-webkit-backdrop-filter: blur(15px);
}
.format-group,
.property-item,
.palette-item,
.accessibility-item {
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
}
}
/* 深色模式支持(如果用户系统设置为深色模式) */
@media (prefers-color-scheme: dark) {
body {
background: linear-gradient(135deg, #1a2e1a 0%, #0f1f0f 50%, #1a2e1a 100%);
}
.container::before {
background-image:
radial-gradient(circle at 20% 80%, rgba(144, 205, 144, 0.05) 0%, transparent 50%),
radial-gradient(circle at 80% 20%, rgba(45, 90, 39, 0.03) 0%, transparent 50%);
}
.input-section,
.result-section {
background: rgba(26, 46, 26, 0.9);
}
.format-group,
.property-item,
.palette-item,
.accessibility-item,
.color-preview {
background: rgba(26, 46, 26, 0.6);
}
.input-group input,
.input-group select {
background: rgba(26, 46, 26, 0.8);
color: #e8f5e8;
border-color: rgba(144, 205, 144, 0.3);
}
.format-group p {
background: rgba(15, 31, 15, 0.8);
color: #e8f5e8;
}
/* 背景样式文件 - 独立管理背景相关样式 */
/* 主体背景 */
body {
background: linear-gradient(135deg, #e8f5e8 0%, #f0fff0 50%, #e8f5e8 100%);
background-attachment: fixed;
background-size: 400% 400%;
animation: gradientShift 15s ease infinite;
}
/* 背景动画 */
@keyframes gradientShift {
0% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
}
/* 容器背景装饰 */
.container::before {
content: '';
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image:
radial-gradient(circle at 20% 80%, rgba(144, 205, 144, 0.1) 0%, transparent 50%),
radial-gradient(circle at 80% 20%, rgba(45, 90, 39, 0.05) 0%, transparent 50%),
radial-gradient(circle at 40% 40%, rgba(144, 205, 144, 0.08) 0%, transparent 50%);
pointer-events: none;
z-index: -1;
}
/* 输入区域背景 */
.input-section {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
}
/* 结果区域背景 */
.result-section {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
}
/* 格式组背景 */
.format-group {
background: rgba(248, 255, 248, 0.8);
backdrop-filter: blur(5px);
-webkit-backdrop-filter: blur(5px);
}
/* 属性项背景 */
.property-item {
background: rgba(248, 255, 248, 0.8);
backdrop-filter: blur(5px);
-webkit-backdrop-filter: blur(5px);
}
/* 调色板项背景 */
.palette-item {
background: rgba(248, 255, 248, 0.8);
backdrop-filter: blur(5px);
-webkit-backdrop-filter: blur(5px);
}
/* 无障碍项背景 */
.accessibility-item {
background: rgba(248, 255, 248, 0.8);
backdrop-filter: blur(5px);
-webkit-backdrop-filter: blur(5px);
}
/* 颜色预览背景 */
.color-preview {
background: rgba(248, 255, 248, 0.6);
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
}
/* 输入框背景 */
.input-group input,
.input-group select {
background: rgba(248, 255, 248, 0.9);
backdrop-filter: blur(5px);
-webkit-backdrop-filter: blur(5px);
}
.input-group input:focus,
.input-group select:focus {
background: rgba(255, 255, 255, 0.95);
}
/* 格式组内部元素背景 */
.format-group p {
background: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(3px);
-webkit-backdrop-filter: blur(3px);
}
/* 手机端背景优化 */
@media (max-width: 767px) {
body {
background: linear-gradient(180deg, #e8f5e8 0%, #f0fff0 50%, #e8f5e8 100%);
background-attachment: scroll; /* 手机端使用scroll避免性能问题 */
}
.container::before {
background-image:
radial-gradient(circle at 30% 70%, rgba(144, 205, 144, 0.08) 0%, transparent 40%),
radial-gradient(circle at 70% 30%, rgba(45, 90, 39, 0.04) 0%, transparent 40%);
}
/* 减少手机端的模糊效果以提升性能 */
.input-section,
.result-section {
backdrop-filter: blur(5px);
-webkit-backdrop-filter: blur(5px);
}
.format-group,
.property-item,
.palette-item,
.accessibility-item {
backdrop-filter: blur(3px);
-webkit-backdrop-filter: blur(3px);
}
}
/* 平板端背景优化 */
@media (min-width: 768px) and (max-width: 1024px) {
.container::before {
background-image:
radial-gradient(circle at 25% 75%, rgba(144, 205, 144, 0.12) 0%, transparent 60%),
radial-gradient(circle at 75% 25%, rgba(45, 90, 39, 0.06) 0%, transparent 60%),
radial-gradient(circle at 50% 50%, rgba(144, 205, 144, 0.04) 0%, transparent 40%);
}
}
/* 电脑端背景优化 */
@media (min-width: 1025px) {
body {
background-size: 300% 300%;
animation-duration: 20s;
}
.container::before {
background-image:
radial-gradient(circle at 15% 85%, rgba(144, 205, 144, 0.15) 0%, transparent 70%),
radial-gradient(circle at 85% 15%, rgba(45, 90, 39, 0.08) 0%, transparent 70%),
radial-gradient(circle at 35% 35%, rgba(144, 205, 144, 0.1) 0%, transparent 50%),
radial-gradient(circle at 65% 65%, rgba(45, 90, 39, 0.05) 0%, transparent 50%);
}
/* 电脑端增强模糊效果 */
.input-section,
.result-section {
backdrop-filter: blur(15px);
-webkit-backdrop-filter: blur(15px);
}
.format-group,
.property-item,
.palette-item,
.accessibility-item {
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
}
}
/* 深色模式支持(如果用户系统设置为深色模式) */
@media (prefers-color-scheme: dark) {
body {
background: linear-gradient(135deg, #1a2e1a 0%, #0f1f0f 50%, #1a2e1a 100%);
}
.container::before {
background-image:
radial-gradient(circle at 20% 80%, rgba(144, 205, 144, 0.05) 0%, transparent 50%),
radial-gradient(circle at 80% 20%, rgba(45, 90, 39, 0.03) 0%, transparent 50%);
}
.input-section,
.result-section {
background: rgba(26, 46, 26, 0.9);
}
.format-group,
.property-item,
.palette-item,
.accessibility-item,
.color-preview {
background: rgba(26, 46, 26, 0.6);
}
.input-group input,
.input-group select {
background: rgba(26, 46, 26, 0.8);
color: #e8f5e8;
border-color: rgba(144, 205, 144, 0.3);
}
.format-group p {
background: rgba(15, 31, 15, 0.8);
color: #e8f5e8;
}
}

View File

@@ -1,187 +1,187 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>随机颜色/颜色转换工具</title>
<link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="background.css">
</head>
<body>
<div class="container">
<header class="header">
<h1>随机颜色/颜色转换工具</h1>
<p class="subtitle">获取随机颜色或转换指定颜色格式</p>
</header>
<main class="main-content">
<div class="input-section">
<div class="input-group">
<label for="colorInput">输入颜色值(可选):</label>
<input type="text" id="colorInput" placeholder="例如: #33AAFF">
</div>
<div class="input-group">
<label for="encodingSelect">输出格式:</label>
<select id="encodingSelect">
<option value="json">JSON</option>
<option value="text">文本</option>
<option value="html">HTML</option>
</select>
</div>
<div class="button-group">
<button id="randomBtn" class="btn btn-primary">获取随机颜色</button>
<button id="convertBtn" class="btn btn-secondary">转换颜色</button>
</div>
</div>
<div class="result-section">
<div class="color-preview">
<div id="colorDisplay" class="color-box"></div>
<div class="color-info">
<h3 id="colorName">颜色名称</h3>
<p id="hexValue">#000000</p>
</div>
</div>
<div class="color-formats">
<div class="format-group">
<h4>RGB</h4>
<div class="format-values">
<span id="rgbR">0</span>
<span id="rgbG">0</span>
<span id="rgbB">0</span>
</div>
<p id="rgbString">rgb(0, 0, 0)</p>
</div>
<div class="format-group">
<h4>HSL</h4>
<div class="format-values">
<span id="hslH"></span>
<span id="hslS">0%</span>
<span id="hslL">0%</span>
</div>
<p id="hslString">hsl(0, 0%, 0%)</p>
</div>
<div class="format-group">
<h4>HSV</h4>
<div class="format-values">
<span id="hsvH"></span>
<span id="hsvS">0%</span>
<span id="hsvV">0%</span>
</div>
<p id="hsvString">hsv(0, 0%, 0%)</p>
</div>
<div class="format-group">
<h4>CMYK</h4>
<div class="format-values">
<span id="cmykC">0%</span>
<span id="cmykM">0%</span>
<span id="cmykY">0%</span>
<span id="cmykK">0%</span>
</div>
<p id="cmykString">cmyk(0%, 0%, 0%, 0%)</p>
</div>
<div class="format-group">
<h4>LAB</h4>
<div class="format-values">
<span id="labL">0</span>
<span id="labA">0</span>
<span id="labB">0</span>
</div>
<p id="labString">lab(0, 0, 0)</p>
</div>
</div>
<div class="color-properties">
<div class="property-item">
<label>亮度:</label>
<span id="brightness">0</span>
</div>
<div class="property-item">
<label>对比度 (白色):</label>
<span id="contrastWhite">0</span>
</div>
<div class="property-item">
<label>对比度 (黑色):</label>
<span id="contrastBlack">0</span>
</div>
<div class="property-item">
<label>最佳文字颜色:</label>
<span id="bestTextColor">#000000</span>
</div>
</div>
<div class="color-palette">
<h4>配色方案</h4>
<div class="palette-group">
<div class="palette-item">
<label>互补色:</label>
<div id="complementary" class="color-sample"></div>
<span id="complementaryHex">#000000</span>
</div>
<div class="palette-item">
<label>类似色:</label>
<div class="analogous-colors">
<div id="analogous1" class="color-sample"></div>
<div id="analogous2" class="color-sample"></div>
</div>
<div class="analogous-hex">
<span id="analogous1Hex">#000000</span>
<span id="analogous2Hex">#000000</span>
</div>
</div>
<div class="palette-item">
<label>三角色:</label>
<div class="triadic-colors">
<div id="triadic1" class="color-sample"></div>
<div id="triadic2" class="color-sample"></div>
</div>
<div class="triadic-hex">
<span id="triadic1Hex">#000000</span>
<span id="triadic2Hex">#000000</span>
</div>
</div>
</div>
</div>
<div class="accessibility-info">
<h4>无障碍性</h4>
<div class="accessibility-grid">
<div class="accessibility-item">
<span>AA 普通文本:</span>
<span id="aaNormal" class="status"></span>
</div>
<div class="accessibility-item">
<span>AA 大文本:</span>
<span id="aaLarge" class="status"></span>
</div>
<div class="accessibility-item">
<span>AAA 普通文本:</span>
<span id="aaaNormal" class="status"></span>
</div>
<div class="accessibility-item">
<span>AAA 大文本:</span>
<span id="aaaLarge" class="status"></span>
</div>
</div>
</div>
</div>
<div class="loading" id="loading" style="display: none;">
<div class="spinner"></div>
<p>正在获取颜色信息...</p>
</div>
<div class="error" id="error" style="display: none;">
<p id="errorMessage">获取颜色信息失败,请稍后重试</p>
</div>
</main>
</div>
<script src="script.js"></script>
</body>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>随机颜色/颜色转换工具</title>
<link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="background.css">
</head>
<body>
<div class="container">
<header class="header">
<h1>随机颜色/颜色转换工具</h1>
<p class="subtitle">获取随机颜色或转换指定颜色格式</p>
</header>
<main class="main-content">
<div class="input-section">
<div class="input-group">
<label for="colorInput">输入颜色值(可选):</label>
<input type="text" id="colorInput" placeholder="例如: #33AAFF">
</div>
<div class="input-group">
<label for="encodingSelect">输出格式:</label>
<select id="encodingSelect">
<option value="json">JSON</option>
<option value="text">文本</option>
<option value="html">HTML</option>
</select>
</div>
<div class="button-group">
<button id="randomBtn" class="btn btn-primary">获取随机颜色</button>
<button id="convertBtn" class="btn btn-secondary">转换颜色</button>
</div>
</div>
<div class="result-section">
<div class="color-preview">
<div id="colorDisplay" class="color-box"></div>
<div class="color-info">
<h3 id="colorName">颜色名称</h3>
<p id="hexValue">#000000</p>
</div>
</div>
<div class="color-formats">
<div class="format-group">
<h4>RGB</h4>
<div class="format-values">
<span id="rgbR">0</span>
<span id="rgbG">0</span>
<span id="rgbB">0</span>
</div>
<p id="rgbString">rgb(0, 0, 0)</p>
</div>
<div class="format-group">
<h4>HSL</h4>
<div class="format-values">
<span id="hslH"></span>
<span id="hslS">0%</span>
<span id="hslL">0%</span>
</div>
<p id="hslString">hsl(0, 0%, 0%)</p>
</div>
<div class="format-group">
<h4>HSV</h4>
<div class="format-values">
<span id="hsvH"></span>
<span id="hsvS">0%</span>
<span id="hsvV">0%</span>
</div>
<p id="hsvString">hsv(0, 0%, 0%)</p>
</div>
<div class="format-group">
<h4>CMYK</h4>
<div class="format-values">
<span id="cmykC">0%</span>
<span id="cmykM">0%</span>
<span id="cmykY">0%</span>
<span id="cmykK">0%</span>
</div>
<p id="cmykString">cmyk(0%, 0%, 0%, 0%)</p>
</div>
<div class="format-group">
<h4>LAB</h4>
<div class="format-values">
<span id="labL">0</span>
<span id="labA">0</span>
<span id="labB">0</span>
</div>
<p id="labString">lab(0, 0, 0)</p>
</div>
</div>
<div class="color-properties">
<div class="property-item">
<label>亮度:</label>
<span id="brightness">0</span>
</div>
<div class="property-item">
<label>对比度 (白色):</label>
<span id="contrastWhite">0</span>
</div>
<div class="property-item">
<label>对比度 (黑色):</label>
<span id="contrastBlack">0</span>
</div>
<div class="property-item">
<label>最佳文字颜色:</label>
<span id="bestTextColor">#000000</span>
</div>
</div>
<div class="color-palette">
<h4>配色方案</h4>
<div class="palette-group">
<div class="palette-item">
<label>互补色:</label>
<div id="complementary" class="color-sample"></div>
<span id="complementaryHex">#000000</span>
</div>
<div class="palette-item">
<label>类似色:</label>
<div class="analogous-colors">
<div id="analogous1" class="color-sample"></div>
<div id="analogous2" class="color-sample"></div>
</div>
<div class="analogous-hex">
<span id="analogous1Hex">#000000</span>
<span id="analogous2Hex">#000000</span>
</div>
</div>
<div class="palette-item">
<label>三角色:</label>
<div class="triadic-colors">
<div id="triadic1" class="color-sample"></div>
<div id="triadic2" class="color-sample"></div>
</div>
<div class="triadic-hex">
<span id="triadic1Hex">#000000</span>
<span id="triadic2Hex">#000000</span>
</div>
</div>
</div>
</div>
<div class="accessibility-info">
<h4>无障碍性</h4>
<div class="accessibility-grid">
<div class="accessibility-item">
<span>AA 普通文本:</span>
<span id="aaNormal" class="status"></span>
</div>
<div class="accessibility-item">
<span>AA 大文本:</span>
<span id="aaLarge" class="status"></span>
</div>
<div class="accessibility-item">
<span>AAA 普通文本:</span>
<span id="aaaNormal" class="status"></span>
</div>
<div class="accessibility-item">
<span>AAA 大文本:</span>
<span id="aaaLarge" class="status"></span>
</div>
</div>
</div>
</div>
<div class="loading" id="loading" style="display: none;">
<div class="spinner"></div>
<p>正在获取颜色信息...</p>
</div>
<div class="error" id="error" style="display: none;">
<p id="errorMessage">获取颜色信息失败,请稍后重试</p>
</div>
</main>
</div>
<script src="script.js"></script>
</body>
</html>

View File

@@ -1,426 +1,426 @@
// 随机颜色/颜色转换工具 JavaScript
class ColorTool {
constructor() {
this.apiUrl = 'https://60s.api.shumengya.top/v2/color';
this.init();
}
init() {
this.bindEvents();
this.hideResultSection();
}
bindEvents() {
const randomBtn = document.getElementById('randomBtn');
const convertBtn = document.getElementById('convertBtn');
const colorInput = document.getElementById('colorInput');
randomBtn.addEventListener('click', () => this.getRandomColor());
convertBtn.addEventListener('click', () => this.convertColor());
// 回车键支持
colorInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
this.convertColor();
}
});
}
hideResultSection() {
const resultSection = document.querySelector('.result-section');
resultSection.style.display = 'none';
}
showResultSection() {
const resultSection = document.querySelector('.result-section');
resultSection.style.display = 'block';
}
showLoading() {
const loading = document.getElementById('loading');
const error = document.getElementById('error');
loading.style.display = 'block';
error.style.display = 'none';
this.hideResultSection();
}
hideLoading() {
const loading = document.getElementById('loading');
loading.style.display = 'none';
}
showError(message) {
const error = document.getElementById('error');
const errorMessage = document.getElementById('errorMessage');
const loading = document.getElementById('loading');
loading.style.display = 'none';
errorMessage.textContent = message;
error.style.display = 'block';
this.hideResultSection();
}
hideError() {
const error = document.getElementById('error');
error.style.display = 'none';
}
async getRandomColor() {
try {
this.showLoading();
const encoding = document.getElementById('encodingSelect').value;
const url = `${this.apiUrl}?encoding=${encoding}`;
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
if (data.code === 200) {
this.displayColorData(data.data);
this.hideLoading();
this.hideError();
this.showResultSection();
} else {
throw new Error(data.message || '获取颜色信息失败');
}
} catch (error) {
console.error('获取随机颜色失败:', error);
this.showError(`获取随机颜色失败: ${error.message}`);
}
}
async convertColor() {
const colorInput = document.getElementById('colorInput');
const colorValue = colorInput.value.trim();
if (!colorValue) {
this.showError('请输入要转换的颜色值');
return;
}
// 简单的颜色格式验证
if (!this.isValidColor(colorValue)) {
this.showError('请输入有效的颜色值(如 #33AAFF');
return;
}
try {
this.showLoading();
const encoding = document.getElementById('encodingSelect').value;
const url = `${this.apiUrl}?color=${encodeURIComponent(colorValue)}&encoding=${encoding}`;
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
if (data.code === 200) {
this.displayColorData(data.data);
this.hideLoading();
this.hideError();
this.showResultSection();
} else {
throw new Error(data.message || '转换颜色失败');
}
} catch (error) {
console.error('转换颜色失败:', error);
this.showError(`转换颜色失败: ${error.message}`);
}
}
isValidColor(color) {
// 支持十六进制颜色格式
const hexPattern = /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/;
// 支持RGB格式
const rgbPattern = /^rgb\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*\)$/;
// 支持HSL格式
const hslPattern = /^hsl\(\s*\d+\s*,\s*\d+%\s*,\s*\d+%\s*\)$/;
return hexPattern.test(color) || rgbPattern.test(color) || hslPattern.test(color);
}
displayColorData(data) {
// 显示主要颜色信息
this.updateColorDisplay(data);
// 显示各种格式
this.updateColorFormats(data);
// 显示颜色属性
this.updateColorProperties(data);
// 显示配色方案
this.updateColorPalette(data);
// 显示无障碍性信息
this.updateAccessibilityInfo(data);
}
updateColorDisplay(data) {
const colorDisplay = document.getElementById('colorDisplay');
const colorName = document.getElementById('colorName');
const hexValue = document.getElementById('hexValue');
colorDisplay.style.backgroundColor = data.hex;
colorName.textContent = data.name || '未知颜色';
hexValue.textContent = data.hex;
}
updateColorFormats(data) {
// RGB
if (data.rgb) {
document.getElementById('rgbR').textContent = data.rgb.r;
document.getElementById('rgbG').textContent = data.rgb.g;
document.getElementById('rgbB').textContent = data.rgb.b;
document.getElementById('rgbString').textContent = data.rgb.string;
}
// HSL
if (data.hsl) {
document.getElementById('hslH').textContent = data.hsl.h + '°';
document.getElementById('hslS').textContent = data.hsl.s + '%';
document.getElementById('hslL').textContent = data.hsl.l + '%';
document.getElementById('hslString').textContent = data.hsl.string;
}
// HSV
if (data.hsv) {
document.getElementById('hsvH').textContent = data.hsv.h + '°';
document.getElementById('hsvS').textContent = data.hsv.s + '%';
document.getElementById('hsvV').textContent = data.hsv.v + '%';
document.getElementById('hsvString').textContent = data.hsv.string;
}
// CMYK
if (data.cmyk) {
document.getElementById('cmykC').textContent = data.cmyk.c + '%';
document.getElementById('cmykM').textContent = data.cmyk.m + '%';
document.getElementById('cmykY').textContent = data.cmyk.y + '%';
document.getElementById('cmykK').textContent = data.cmyk.k + '%';
document.getElementById('cmykString').textContent = data.cmyk.string;
}
// LAB
if (data.lab) {
document.getElementById('labL').textContent = data.lab.l;
document.getElementById('labA').textContent = data.lab.a;
document.getElementById('labB').textContent = data.lab.b;
document.getElementById('labString').textContent = data.lab.string;
}
}
updateColorProperties(data) {
// 亮度
if (data.brightness !== undefined) {
document.getElementById('brightness').textContent = data.brightness.toFixed(2);
}
// 对比度
if (data.contrast) {
document.getElementById('contrastWhite').textContent = data.contrast.white.toFixed(2);
document.getElementById('contrastBlack').textContent = data.contrast.black.toFixed(2);
}
// 最佳文字颜色
if (data.accessibility && data.accessibility.best_text_color) {
const bestTextColor = document.getElementById('bestTextColor');
bestTextColor.textContent = data.accessibility.best_text_color;
bestTextColor.style.color = data.accessibility.best_text_color;
}
}
updateColorPalette(data) {
// 互补色
if (data.complementary) {
const complementary = document.getElementById('complementary');
const complementaryHex = document.getElementById('complementaryHex');
complementary.style.backgroundColor = data.complementary;
complementaryHex.textContent = data.complementary;
}
// 类似色
if (data.analogous && data.analogous.length >= 2) {
const analogous1 = document.getElementById('analogous1');
const analogous2 = document.getElementById('analogous2');
const analogous1Hex = document.getElementById('analogous1Hex');
const analogous2Hex = document.getElementById('analogous2Hex');
analogous1.style.backgroundColor = data.analogous[0];
analogous2.style.backgroundColor = data.analogous[1];
analogous1Hex.textContent = data.analogous[0];
analogous2Hex.textContent = data.analogous[1];
}
// 三角色
if (data.triadic && data.triadic.length >= 2) {
const triadic1 = document.getElementById('triadic1');
const triadic2 = document.getElementById('triadic2');
const triadic1Hex = document.getElementById('triadic1Hex');
const triadic2Hex = document.getElementById('triadic2Hex');
triadic1.style.backgroundColor = data.triadic[0];
triadic2.style.backgroundColor = data.triadic[1];
triadic1Hex.textContent = data.triadic[0];
triadic2Hex.textContent = data.triadic[1];
}
}
updateAccessibilityInfo(data) {
if (data.accessibility) {
const aaNormal = document.getElementById('aaNormal');
const aaLarge = document.getElementById('aaLarge');
const aaaNormal = document.getElementById('aaaNormal');
const aaaLarge = document.getElementById('aaaLarge');
this.updateAccessibilityStatus(aaNormal, data.accessibility.aa_normal);
this.updateAccessibilityStatus(aaLarge, data.accessibility.aa_large);
this.updateAccessibilityStatus(aaaNormal, data.accessibility.aaa_normal);
this.updateAccessibilityStatus(aaaLarge, data.accessibility.aaa_large);
}
}
updateAccessibilityStatus(element, status) {
element.textContent = status ? '通过' : '未通过';
element.className = 'status ' + (status ? 'pass' : 'fail');
}
// 复制颜色值到剪贴板
copyToClipboard(text) {
if (navigator.clipboard) {
navigator.clipboard.writeText(text).then(() => {
this.showToast('已复制到剪贴板');
}).catch(err => {
console.error('复制失败:', err);
this.fallbackCopyTextToClipboard(text);
});
} else {
this.fallbackCopyTextToClipboard(text);
}
}
fallbackCopyTextToClipboard(text) {
const textArea = document.createElement('textarea');
textArea.value = text;
textArea.style.top = '0';
textArea.style.left = '0';
textArea.style.position = 'fixed';
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
try {
const successful = document.execCommand('copy');
if (successful) {
this.showToast('已复制到剪贴板');
} else {
this.showToast('复制失败');
}
} catch (err) {
console.error('复制失败:', err);
this.showToast('复制失败');
}
document.body.removeChild(textArea);
}
showToast(message) {
// 创建简单的提示框
const toast = document.createElement('div');
toast.textContent = message;
toast.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: #2d5a27;
color: white;
padding: 12px 20px;
border-radius: 8px;
z-index: 1000;
font-size: 14px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
animation: slideIn 0.3s ease;
`;
// 添加动画样式
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);
setTimeout(() => {
toast.style.animation = 'slideIn 0.3s ease reverse';
setTimeout(() => {
document.body.removeChild(toast);
document.head.removeChild(style);
}, 300);
}, 2000);
}
}
// 添加点击复制功能
function addCopyListeners() {
const colorTool = window.colorTool;
// 为所有颜色值添加点击复制功能
document.addEventListener('click', (e) => {
const target = e.target;
// 检查是否点击了颜色值相关元素
if (target.id === 'hexValue' ||
target.id === 'rgbString' ||
target.id === 'hslString' ||
target.id === 'hsvString' ||
target.id === 'cmykString' ||
target.id === 'labString' ||
target.id === 'complementaryHex' ||
target.id === 'analogous1Hex' ||
target.id === 'analogous2Hex' ||
target.id === 'triadic1Hex' ||
target.id === 'triadic2Hex') {
const text = target.textContent;
if (text && colorTool) {
colorTool.copyToClipboard(text);
}
}
});
}
// 页面加载完成后初始化
document.addEventListener('DOMContentLoaded', () => {
window.colorTool = new ColorTool();
addCopyListeners();
// 添加复制提示
const style = document.createElement('style');
style.textContent = `
#hexValue, #rgbString, #hslString, #hsvString, #cmykString, #labString,
#complementaryHex, #analogous1Hex, #analogous2Hex, #triadic1Hex, #triadic2Hex {
cursor: pointer;
transition: all 0.2s ease;
}
#hexValue:hover, #rgbString:hover, #hslString:hover, #hsvString:hover,
#cmykString:hover, #labString:hover, #complementaryHex:hover,
#analogous1Hex:hover, #analogous2Hex:hover, #triadic1Hex:hover, #triadic2Hex:hover {
background: rgba(45, 90, 39, 0.1);
border-radius: 4px;
padding: 2px 4px;
margin: -2px -4px;
}
`;
document.head.appendChild(style);
// 随机颜色/颜色转换工具 JavaScript
class ColorTool {
constructor() {
this.apiUrl = 'https://60s.api.shumengya.top/v2/color';
this.init();
}
init() {
this.bindEvents();
this.hideResultSection();
}
bindEvents() {
const randomBtn = document.getElementById('randomBtn');
const convertBtn = document.getElementById('convertBtn');
const colorInput = document.getElementById('colorInput');
randomBtn.addEventListener('click', () => this.getRandomColor());
convertBtn.addEventListener('click', () => this.convertColor());
// 回车键支持
colorInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
this.convertColor();
}
});
}
hideResultSection() {
const resultSection = document.querySelector('.result-section');
resultSection.style.display = 'none';
}
showResultSection() {
const resultSection = document.querySelector('.result-section');
resultSection.style.display = 'block';
}
showLoading() {
const loading = document.getElementById('loading');
const error = document.getElementById('error');
loading.style.display = 'block';
error.style.display = 'none';
this.hideResultSection();
}
hideLoading() {
const loading = document.getElementById('loading');
loading.style.display = 'none';
}
showError(message) {
const error = document.getElementById('error');
const errorMessage = document.getElementById('errorMessage');
const loading = document.getElementById('loading');
loading.style.display = 'none';
errorMessage.textContent = message;
error.style.display = 'block';
this.hideResultSection();
}
hideError() {
const error = document.getElementById('error');
error.style.display = 'none';
}
async getRandomColor() {
try {
this.showLoading();
const encoding = document.getElementById('encodingSelect').value;
const url = `${this.apiUrl}?encoding=${encoding}`;
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
if (data.code === 200) {
this.displayColorData(data.data);
this.hideLoading();
this.hideError();
this.showResultSection();
} else {
throw new Error(data.message || '获取颜色信息失败');
}
} catch (error) {
console.error('获取随机颜色失败:', error);
this.showError(`获取随机颜色失败: ${error.message}`);
}
}
async convertColor() {
const colorInput = document.getElementById('colorInput');
const colorValue = colorInput.value.trim();
if (!colorValue) {
this.showError('请输入要转换的颜色值');
return;
}
// 简单的颜色格式验证
if (!this.isValidColor(colorValue)) {
this.showError('请输入有效的颜色值(如 #33AAFF');
return;
}
try {
this.showLoading();
const encoding = document.getElementById('encodingSelect').value;
const url = `${this.apiUrl}?color=${encodeURIComponent(colorValue)}&encoding=${encoding}`;
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
if (data.code === 200) {
this.displayColorData(data.data);
this.hideLoading();
this.hideError();
this.showResultSection();
} else {
throw new Error(data.message || '转换颜色失败');
}
} catch (error) {
console.error('转换颜色失败:', error);
this.showError(`转换颜色失败: ${error.message}`);
}
}
isValidColor(color) {
// 支持十六进制颜色格式
const hexPattern = /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/;
// 支持RGB格式
const rgbPattern = /^rgb\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*\)$/;
// 支持HSL格式
const hslPattern = /^hsl\(\s*\d+\s*,\s*\d+%\s*,\s*\d+%\s*\)$/;
return hexPattern.test(color) || rgbPattern.test(color) || hslPattern.test(color);
}
displayColorData(data) {
// 显示主要颜色信息
this.updateColorDisplay(data);
// 显示各种格式
this.updateColorFormats(data);
// 显示颜色属性
this.updateColorProperties(data);
// 显示配色方案
this.updateColorPalette(data);
// 显示无障碍性信息
this.updateAccessibilityInfo(data);
}
updateColorDisplay(data) {
const colorDisplay = document.getElementById('colorDisplay');
const colorName = document.getElementById('colorName');
const hexValue = document.getElementById('hexValue');
colorDisplay.style.backgroundColor = data.hex;
colorName.textContent = data.name || '未知颜色';
hexValue.textContent = data.hex;
}
updateColorFormats(data) {
// RGB
if (data.rgb) {
document.getElementById('rgbR').textContent = data.rgb.r;
document.getElementById('rgbG').textContent = data.rgb.g;
document.getElementById('rgbB').textContent = data.rgb.b;
document.getElementById('rgbString').textContent = data.rgb.string;
}
// HSL
if (data.hsl) {
document.getElementById('hslH').textContent = data.hsl.h + '°';
document.getElementById('hslS').textContent = data.hsl.s + '%';
document.getElementById('hslL').textContent = data.hsl.l + '%';
document.getElementById('hslString').textContent = data.hsl.string;
}
// HSV
if (data.hsv) {
document.getElementById('hsvH').textContent = data.hsv.h + '°';
document.getElementById('hsvS').textContent = data.hsv.s + '%';
document.getElementById('hsvV').textContent = data.hsv.v + '%';
document.getElementById('hsvString').textContent = data.hsv.string;
}
// CMYK
if (data.cmyk) {
document.getElementById('cmykC').textContent = data.cmyk.c + '%';
document.getElementById('cmykM').textContent = data.cmyk.m + '%';
document.getElementById('cmykY').textContent = data.cmyk.y + '%';
document.getElementById('cmykK').textContent = data.cmyk.k + '%';
document.getElementById('cmykString').textContent = data.cmyk.string;
}
// LAB
if (data.lab) {
document.getElementById('labL').textContent = data.lab.l;
document.getElementById('labA').textContent = data.lab.a;
document.getElementById('labB').textContent = data.lab.b;
document.getElementById('labString').textContent = data.lab.string;
}
}
updateColorProperties(data) {
// 亮度
if (data.brightness !== undefined) {
document.getElementById('brightness').textContent = data.brightness.toFixed(2);
}
// 对比度
if (data.contrast) {
document.getElementById('contrastWhite').textContent = data.contrast.white.toFixed(2);
document.getElementById('contrastBlack').textContent = data.contrast.black.toFixed(2);
}
// 最佳文字颜色
if (data.accessibility && data.accessibility.best_text_color) {
const bestTextColor = document.getElementById('bestTextColor');
bestTextColor.textContent = data.accessibility.best_text_color;
bestTextColor.style.color = data.accessibility.best_text_color;
}
}
updateColorPalette(data) {
// 互补色
if (data.complementary) {
const complementary = document.getElementById('complementary');
const complementaryHex = document.getElementById('complementaryHex');
complementary.style.backgroundColor = data.complementary;
complementaryHex.textContent = data.complementary;
}
// 类似色
if (data.analogous && data.analogous.length >= 2) {
const analogous1 = document.getElementById('analogous1');
const analogous2 = document.getElementById('analogous2');
const analogous1Hex = document.getElementById('analogous1Hex');
const analogous2Hex = document.getElementById('analogous2Hex');
analogous1.style.backgroundColor = data.analogous[0];
analogous2.style.backgroundColor = data.analogous[1];
analogous1Hex.textContent = data.analogous[0];
analogous2Hex.textContent = data.analogous[1];
}
// 三角色
if (data.triadic && data.triadic.length >= 2) {
const triadic1 = document.getElementById('triadic1');
const triadic2 = document.getElementById('triadic2');
const triadic1Hex = document.getElementById('triadic1Hex');
const triadic2Hex = document.getElementById('triadic2Hex');
triadic1.style.backgroundColor = data.triadic[0];
triadic2.style.backgroundColor = data.triadic[1];
triadic1Hex.textContent = data.triadic[0];
triadic2Hex.textContent = data.triadic[1];
}
}
updateAccessibilityInfo(data) {
if (data.accessibility) {
const aaNormal = document.getElementById('aaNormal');
const aaLarge = document.getElementById('aaLarge');
const aaaNormal = document.getElementById('aaaNormal');
const aaaLarge = document.getElementById('aaaLarge');
this.updateAccessibilityStatus(aaNormal, data.accessibility.aa_normal);
this.updateAccessibilityStatus(aaLarge, data.accessibility.aa_large);
this.updateAccessibilityStatus(aaaNormal, data.accessibility.aaa_normal);
this.updateAccessibilityStatus(aaaLarge, data.accessibility.aaa_large);
}
}
updateAccessibilityStatus(element, status) {
element.textContent = status ? '通过' : '未通过';
element.className = 'status ' + (status ? 'pass' : 'fail');
}
// 复制颜色值到剪贴板
copyToClipboard(text) {
if (navigator.clipboard) {
navigator.clipboard.writeText(text).then(() => {
this.showToast('已复制到剪贴板');
}).catch(err => {
console.error('复制失败:', err);
this.fallbackCopyTextToClipboard(text);
});
} else {
this.fallbackCopyTextToClipboard(text);
}
}
fallbackCopyTextToClipboard(text) {
const textArea = document.createElement('textarea');
textArea.value = text;
textArea.style.top = '0';
textArea.style.left = '0';
textArea.style.position = 'fixed';
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
try {
const successful = document.execCommand('copy');
if (successful) {
this.showToast('已复制到剪贴板');
} else {
this.showToast('复制失败');
}
} catch (err) {
console.error('复制失败:', err);
this.showToast('复制失败');
}
document.body.removeChild(textArea);
}
showToast(message) {
// 创建简单的提示框
const toast = document.createElement('div');
toast.textContent = message;
toast.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: #2d5a27;
color: white;
padding: 12px 20px;
border-radius: 8px;
z-index: 1000;
font-size: 14px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
animation: slideIn 0.3s ease;
`;
// 添加动画样式
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);
setTimeout(() => {
toast.style.animation = 'slideIn 0.3s ease reverse';
setTimeout(() => {
document.body.removeChild(toast);
document.head.removeChild(style);
}, 300);
}, 2000);
}
}
// 添加点击复制功能
function addCopyListeners() {
const colorTool = window.colorTool;
// 为所有颜色值添加点击复制功能
document.addEventListener('click', (e) => {
const target = e.target;
// 检查是否点击了颜色值相关元素
if (target.id === 'hexValue' ||
target.id === 'rgbString' ||
target.id === 'hslString' ||
target.id === 'hsvString' ||
target.id === 'cmykString' ||
target.id === 'labString' ||
target.id === 'complementaryHex' ||
target.id === 'analogous1Hex' ||
target.id === 'analogous2Hex' ||
target.id === 'triadic1Hex' ||
target.id === 'triadic2Hex') {
const text = target.textContent;
if (text && colorTool) {
colorTool.copyToClipboard(text);
}
}
});
}
// 页面加载完成后初始化
document.addEventListener('DOMContentLoaded', () => {
window.colorTool = new ColorTool();
addCopyListeners();
// 添加复制提示
const style = document.createElement('style');
style.textContent = `
#hexValue, #rgbString, #hslString, #hsvString, #cmykString, #labString,
#complementaryHex, #analogous1Hex, #analogous2Hex, #triadic1Hex, #triadic2Hex {
cursor: pointer;
transition: all 0.2s ease;
}
#hexValue:hover, #rgbString:hover, #hslString:hover, #hsvString:hover,
#cmykString:hover, #labString:hover, #complementaryHex:hover,
#analogous1Hex:hover, #analogous2Hex:hover, #triadic1Hex:hover, #triadic2Hex:hover {
background: rgba(45, 90, 39, 0.1);
border-radius: 4px;
padding: 2px 4px;
margin: -2px -4px;
}
`;
document.head.appendChild(style);
});

View File

@@ -1,60 +1,60 @@
{
"code": 200,
"message": "获取成功。数据来自官方/权威源头,以确保稳定与实时。开源地址 https://github.com/vikiboss/60s反馈群 595941841",
"data": {
"hex": "#A59619",
"name": "红色系",
"rgb": {
"r": 165,
"g": 150,
"b": 25,
"string": "rgb(165, 150, 25)"
},
"hsl": {
"h": 54,
"s": 74,
"l": 37,
"string": "hsl(54, 74%, 37%)"
},
"hsv": {
"h": 54,
"s": 85,
"v": 65,
"string": "hsv(54, 85%, 65%)"
},
"cmyk": {
"c": 0,
"m": 9,
"y": 85,
"k": 35,
"string": "cmyk(0%, 9%, 85%, 35%)"
},
"lab": {
"l": 62,
"a": -7,
"b": 61,
"string": "lab(62, -7, 61)"
},
"brightness": 140.235,
"contrast": {
"white": 3.01,
"black": 6.98
},
"accessibility": {
"aa_normal": true,
"aa_large": true,
"aaa_normal": false,
"aaa_large": true,
"best_text_color": "#000000"
},
"complementary": "#1926A4",
"analogous": [
"#A45019",
"#6CA419"
],
"triadic": [
"#19A496",
"#9619A4"
]
}
{
"code": 200,
"message": "获取成功。数据来自官方/权威源头,以确保稳定与实时。开源地址 https://github.com/vikiboss/60s反馈群 595941841",
"data": {
"hex": "#A59619",
"name": "红色系",
"rgb": {
"r": 165,
"g": 150,
"b": 25,
"string": "rgb(165, 150, 25)"
},
"hsl": {
"h": 54,
"s": 74,
"l": 37,
"string": "hsl(54, 74%, 37%)"
},
"hsv": {
"h": 54,
"s": 85,
"v": 65,
"string": "hsv(54, 85%, 65%)"
},
"cmyk": {
"c": 0,
"m": 9,
"y": 85,
"k": 35,
"string": "cmyk(0%, 9%, 85%, 35%)"
},
"lab": {
"l": 62,
"a": -7,
"b": 61,
"string": "lab(62, -7, 61)"
},
"brightness": 140.235,
"contrast": {
"white": 3.01,
"black": 6.98
},
"accessibility": {
"aa_normal": true,
"aa_large": true,
"aaa_normal": false,
"aaa_large": true,
"best_text_color": "#000000"
},
"complementary": "#1926A4",
"analogous": [
"#A45019",
"#6CA419"
],
"triadic": [
"#19A496",
"#9619A4"
]
}
}