优化结果

This commit is contained in:
2025-09-15 19:08:47 +08:00
parent 72084a8782
commit dcfa89e63c
357 changed files with 16156 additions and 1589 deletions

View File

@@ -0,0 +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));
}
}

View File

@@ -0,0 +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>
</html>

View File

@@ -0,0 +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();
});
})();

View File

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

View File

@@ -0,0 +1,66 @@
{
"code": 200,
"message": "获取成功。数据来自官方/权威源头,以确保稳定与实时。开源地址 https://github.com/vikiboss/60s反馈群 595941841",
"data": [
{
"id": "9aa227e2ba294bb1a95c95fde892eb31",
"title": "《Totally Reliable Delivery Service》 Standard Edition",
"cover": "https://cdn1.epicgames.com/52b90f9a982a404781b189f6a7903226/offer/EGS_TotallyReliableDeliveryService_WereFiveGames_S1-2560x1440-47e6e9562d62705a75ea7b7096d0b8dc.jpg",
"original_price": 52,
"original_price_desc": "¥52.00",
"description": "穿好护腰护具发动货车送货的时间到啦在一个高度互动的沙盒世界中与最多三位好友一起随意地完成送货。货物已试投这就是我们靠谱快递Totally Reliable Delivery Service的品质保证",
"seller": "Infogrames LLC",
"is_free_now": true,
"free_start": "2025/08/14 23:00:00",
"free_start_at": 1755183600000,
"free_end": "2025/08/21 23:00:00",
"free_end_at": 1755788400000,
"link": "https://store.epicgames.com/store/zh-CN/p/totally-reliable-delivery-service/home"
},
{
"id": "8ea3500dc38e4f429702bf889c172d3d",
"title": "Hidden Folks",
"cover": "https://cdn1.epicgames.com/spt-assets/7bfd56b0586348dcb139945d9e59f988/hidden-folks-1b7hh.png",
"original_price": 47,
"original_price_desc": "¥47.00",
"description": "Search for hidden folks in hand-drawn, interactive, miniature landscapes. Unfurl tent flaps, cut through bushes, slam doors, and poke some crocodiles! Rooooaaaarrrr!!!!!",
"seller": "Adriaan de Jongh",
"is_free_now": true,
"free_start": "2025/08/14 23:00:00",
"free_start_at": 1755183600000,
"free_end": "2025/08/21 23:00:00",
"free_end_at": 1755788400000,
"link": "https://store.epicgames.com/store/zh-CN/p/hidden-folks-239d16"
},
{
"id": "4cbb6c3704d240f19c3dd5f5cb2b0cb4",
"title": "Kamaeru",
"cover": "https://cdn1.epicgames.com/spt-assets/44313cfbb62b4df5801d0c8d541c2624/kamaeru-40asc.png",
"original_price": 62,
"original_price_desc": "¥62.00",
"description": "Foster a sanctuary for frogs and restore the biodiversity of the wetlands in Kamaeru, a cozy frog collecting game, where you take pictures of frogs, play mini-games and decorate your habitat. Hop right to it!",
"seller": "Armor Games Studios",
"is_free_now": false,
"free_start": "2025/08/21 23:00:00",
"free_start_at": 1755788400000,
"free_end": "2025/08/28 23:00:00",
"free_end_at": 1756393200000,
"link": "https://store.epicgames.com/store/zh-CN/p/kamaeru-0c301e"
},
{
"id": "0d9a533f0e684cc18620a8f408e8e72c",
"title": "Strange Horticulture",
"cover": "https://cdn1.epicgames.com/spt-assets/15e8e3eba65a4763a815d6eae1d763b2/strange-horticulture-offer-2wghv.png",
"original_price": 45,
"original_price_desc": "¥45.00",
"description": "款神秘学解谜游戏,你将扮演当地植物商店的店主,寻找并识别新的植物,悠闲撸猫,与女巫团体交谈,或加入异教。收集各种强大的植物,用它们来影响故事走向,揭开昂德米尔镇的黑暗谜团。",
"seller": "Iceberg Interactive",
"is_free_now": false,
"free_start": "2025/08/21 23:00:00",
"free_start_at": 1755788400000,
"free_end": "2025/08/28 23:00:00",
"free_end_at": 1756393200000,
"link": "https://store.epicgames.com/store/zh-CN/p/strange-horticulture-360e80"
}
]
}

View File

@@ -0,0 +1,233 @@
/* 动态背景样式 */
body::before {
content: '';
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(135deg, #667eea 0%, #764ba2 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(120, 119, 198, 0.3) 0%, transparent 50%),
radial-gradient(circle at 80% 20%, rgba(255, 119, 198, 0.3) 0%, transparent 50%),
radial-gradient(circle at 40% 40%, rgba(120, 219, 255, 0.3) 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(120, 119, 198, 0.3) 0%, transparent 50%),
radial-gradient(circle at 80% 20%, rgba(255, 119, 198, 0.3) 0%, transparent 50%),
radial-gradient(circle at 40% 40%, rgba(120, 219, 255, 0.3) 0%, transparent 50%);
}
25% {
background:
radial-gradient(circle at 60% 30%, rgba(120, 119, 198, 0.3) 0%, transparent 50%),
radial-gradient(circle at 30% 70%, rgba(255, 119, 198, 0.3) 0%, transparent 50%),
radial-gradient(circle at 80% 80%, rgba(120, 219, 255, 0.3) 0%, transparent 50%);
}
50% {
background:
radial-gradient(circle at 80% 60%, rgba(120, 119, 198, 0.3) 0%, transparent 50%),
radial-gradient(circle at 20% 30%, rgba(255, 119, 198, 0.3) 0%, transparent 50%),
radial-gradient(circle at 60% 70%, rgba(120, 219, 255, 0.3) 0%, transparent 50%);
}
75% {
background:
radial-gradient(circle at 40% 90%, rgba(120, 119, 198, 0.3) 0%, transparent 50%),
radial-gradient(circle at 70% 10%, rgba(255, 119, 198, 0.3) 0%, transparent 50%),
radial-gradient(circle at 20% 60%, rgba(120, 219, 255, 0.3) 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

@@ -0,0 +1,445 @@
/* 全局样式重置 */
* {
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;
}
/* 容器样式 */
.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(74, 144, 226, 0.1), rgba(80, 200, 120, 0.1));
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, #4a90e2, #50c878);
-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, #4a90e2, #50c878);
-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, #4a90e2, #50c878);
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(74, 144, 226, 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(74, 144, 226, 0.4);
background: linear-gradient(135deg, #3a7bc8, #40a868);
}
.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

@@ -0,0 +1,139 @@
<!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-server"></i>
<span class="label">数据来源:</span>
<span class="value">60s.viki.moe</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>
<!-- 页脚 -->
<footer class="footer">
<p>&copy; 2024 公网IP地址查询工具 | 数据来源: 60s.viki.moe</p>
</footer>
</div>
<script src="js/script.js"></script>
</body>
</html>

View File

@@ -0,0 +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();
// 导出到全局作用域(用于调试)
window.IPQueryApp = app;

View File

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

View File

@@ -0,0 +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 (时区)
//
// 使API

View File

@@ -0,0 +1,575 @@
/* 农历主题背景样式 - 动态调节版本 */
body {
background: linear-gradient(135deg,
#fff8dc 0%, /* 玉米丝色 */
#ffd700 20%, /* 金黄色 */
#ffcc00 40%, /* 亮金色 */
#daa520 60%, /* 深金色 */
#b8860b 80%, /* 暗金色 */
#fff8dc 100% /* 玉米丝色 */
);
background-size: 400% 400%;
animation: goldenShift 25s ease infinite;
background-attachment: fixed;
min-height: 100vh;
position: relative;
}
@keyframes goldenShift {
0% { background-position: 0% 50%; }
25% { background-position: 100% 50%; }
50% { background-position: 100% 100%; }
75% { background-position: 0% 100%; }
100% { background-position: 0% 50%; }
}
/* 动态颜色调节系统 */
.adaptive-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background:
radial-gradient(circle at 20% 30%, rgba(255, 255, 255, 0.1) 0%, transparent 50%),
radial-gradient(circle at 80% 70%, rgba(255, 255, 255, 0.15) 0%, transparent 50%),
linear-gradient(45deg, rgba(255, 255, 255, 0.1) 0%, rgba(255, 255, 255, 0.2) 100%);
pointer-events: none;
z-index: 1;
animation: adaptiveShift 60s ease infinite;
}
@keyframes adaptiveShift {
0% {
background:
radial-gradient(circle at 20% 30%, rgba(255, 255, 255, 0.1) 0%, transparent 50%),
radial-gradient(circle at 80% 70%, rgba(255, 255, 255, 0.15) 0%, transparent 50%),
linear-gradient(45deg, rgba(255, 255, 255, 0.1) 0%, rgba(255, 255, 255, 0.2) 100%);
}
25% {
background:
radial-gradient(circle at 70% 20%, rgba(255, 255, 255, 0.2) 0%, transparent 50%),
radial-gradient(circle at 30% 80%, rgba(255, 255, 255, 0.1) 0%, transparent 50%),
linear-gradient(135deg, rgba(255, 255, 255, 0.15) 0%, rgba(255, 255, 255, 0.25) 100%);
}
50% {
background:
radial-gradient(circle at 50% 50%, rgba(255, 255, 255, 0.15) 0%, transparent 50%),
radial-gradient(circle at 10% 90%, rgba(255, 255, 255, 0.12) 0%, transparent 50%),
linear-gradient(225deg, rgba(255, 255, 255, 0.12) 0%, rgba(255, 255, 255, 0.22) 100%);
}
75% {
background:
radial-gradient(circle at 90% 60%, rgba(255, 255, 255, 0.18) 0%, transparent 50%),
radial-gradient(circle at 40% 10%, rgba(255, 255, 255, 0.08) 0%, transparent 50%),
linear-gradient(315deg, rgba(255, 255, 255, 0.1) 0%, rgba(255, 255, 255, 0.2) 100%);
}
100% {
background:
radial-gradient(circle at 20% 30%, rgba(255, 255, 255, 0.1) 0%, transparent 50%),
radial-gradient(circle at 80% 70%, rgba(255, 255, 255, 0.15) 0%, transparent 50%),
linear-gradient(45deg, rgba(255, 255, 255, 0.1) 0%, rgba(255, 255, 255, 0.2) 100%);
}
}
/* 高清稻穗贴图层 */
body::before {
content: '';
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image:
/* 主稻穗束 - 高清细节 */
radial-gradient(ellipse 1.5px 12px at 50% 45%, #DAA520 0%, #B8860B 30%, transparent 80%),
radial-gradient(ellipse 1px 10px at 48% 50%, #FFD700 0%, #DAA520 40%, transparent 75%),
radial-gradient(ellipse 1.2px 11px at 52% 48%, #FFCC00 0%, #B8860B 35%, transparent 78%),
radial-gradient(ellipse 0.8px 9px at 49% 52%, #F4A460 0%, #DAA520 45%, transparent 70%),
radial-gradient(ellipse 1.3px 13px at 51% 46%, #DEB887 0%, #B8860B 38%, transparent 82%),
/* 次级稻穗 */
radial-gradient(ellipse 1px 8px at 30% 35%, #FFD700 0%, #DAA520 50%, transparent 75%),
radial-gradient(ellipse 0.9px 7px at 32% 38%, #FFCC00 0%, #B8860B 45%, transparent 70%),
radial-gradient(ellipse 1.1px 9px at 28% 36%, #DEB887 0%, #DAA520 40%, transparent 78%),
radial-gradient(ellipse 1px 8px at 70% 65%, #FFD700 0%, #DAA520 50%, transparent 75%),
radial-gradient(ellipse 0.8px 7px at 72% 68%, #F4A460 0%, #B8860B 45%, transparent 70%),
radial-gradient(ellipse 1.2px 9px at 68% 66%, #FFCC00 0%, #DAA520 40%, transparent 78%),
/* 散落稻粒 */
radial-gradient(ellipse 0.5px 4px at 20% 80%, #FFD700 0%, transparent 60%),
radial-gradient(ellipse 0.6px 5px at 80% 20%, #FFCC00 0%, transparent 65%),
radial-gradient(ellipse 0.4px 3px at 15% 25%, #DEB887 0%, transparent 55%),
radial-gradient(ellipse 0.7px 6px at 85% 75%, #DAA520 0%, transparent 70%),
/* 稻穗茎秆 - 更细致 */
linear-gradient(88deg, transparent 49%, #9ACD32 49.5%, #8FBC8F 50%, #9ACD32 50.5%, transparent 51%),
linear-gradient(92deg, transparent 49%, #8FBC8F 49.5%, #228B22 50%, #8FBC8F 50.5%, transparent 51%),
linear-gradient(85deg, transparent 49%, #32CD32 49.5%, #9ACD32 50%, #32CD32 50.5%, transparent 51%);
background-size:
25px 25px, 24px 24px, 26px 26px, 23px 23px, 27px 27px,
20px 20px, 19px 19px, 21px 21px,
22px 22px, 18px 18px, 23px 23px,
15px 15px, 16px 16px, 14px 14px, 17px 17px,
80px 80px, 85px 85px, 75px 75px;
background-position:
0 0, 12px 12px, 6px 18px, 18px 6px, 3px 21px,
40px 40px, 52px 48px, 35px 55px,
120px 120px, 135px 115px, 110px 130px,
200px 200px, 220px 180px, 180px 220px, 240px 160px,
0 0, 40px 40px, 20px 60px;
opacity: 0.25;
pointer-events: none;
z-index: -2;
animation: wheatSway 20s ease-in-out infinite;
}
@keyframes wheatSway {
0%, 100% {
transform: translateX(0) rotate(0deg);
}
25% {
transform: translateX(5px) rotate(0.5deg);
}
50% {
transform: translateX(-3px) rotate(-0.3deg);
}
75% {
transform: translateX(2px) rotate(0.2deg);
}
}
/* 大型稻穗背景层 */
body::after {
content: '';
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image:
/* 主稻穗茎秆 - 右侧大型 */
linear-gradient(85deg, transparent 45%, #9ACD32 47%, #8FBC8F 48%, #228B22 49%, #8FBC8F 50%, #9ACD32 51%, transparent 53%),
linear-gradient(87deg, transparent 46%, #32CD32 47.5%, #9ACD32 48.5%, #8FBC8F 49.5%, #9ACD32 50.5%, #32CD32 51.5%, transparent 54%),
/* 主稻穗穗头 - 大型椭圆稻粒群 */
radial-gradient(ellipse 8px 25px at 75% 15%, #FFD700 0%, #DAA520 30%, #B8860B 60%, transparent 85%),
radial-gradient(ellipse 7px 23px at 77% 18%, #FFCC00 0%, #DAA520 35%, transparent 80%),
radial-gradient(ellipse 9px 27px at 73% 12%, #F4A460 0%, #B8860B 40%, transparent 88%),
radial-gradient(ellipse 6px 22px at 79% 20%, #DEB887 0%, #DAA520 45%, transparent 75%),
radial-gradient(ellipse 8px 24px at 75% 16%, #FFD700 0%, #B8860B 38%, transparent 82%),
/* 稻穗分支 */
radial-gradient(ellipse 5px 18px at 72% 25%, #FFCC00 0%, #DAA520 50%, transparent 75%),
radial-gradient(ellipse 4px 16px at 78% 28%, #F4A460 0%, #B8860B 45%, transparent 70%),
radial-gradient(ellipse 6px 20px at 70% 22%, #DEB887 0%, #DAA520 40%, transparent 78%),
radial-gradient(ellipse 5px 17px at 80% 30%, #FFD700 0%, #B8860B 42%, transparent 76%),
/* 左侧稻穗茎秆 */
linear-gradient(95deg, transparent 15%, #9ACD32 17%, #8FBC8F 18%, #228B22 19%, #8FBC8F 20%, #9ACD32 21%, transparent 23%),
/* 左侧稻穗穗头 */
radial-gradient(ellipse 6px 20px at 25% 25%, #FFD700 0%, #DAA520 30%, transparent 80%),
radial-gradient(ellipse 5px 18px at 27% 28%, #FFCC00 0%, #B8860B 35%, transparent 75%),
radial-gradient(ellipse 7px 22px at 23% 22%, #F4A460 0%, #DAA520 40%, transparent 85%),
/* 麦田远景效果 */
linear-gradient(180deg, transparent 70%, rgba(255, 215, 0, 0.1) 75%, rgba(218, 165, 32, 0.15) 85%, rgba(255, 215, 0, 0.2) 95%, rgba(255, 215, 0, 0.25) 100%),
/* 散落稻粒 */
radial-gradient(ellipse 2px 8px at 60% 40%, #FFD700 0%, transparent 60%),
radial-gradient(ellipse 1.5px 6px at 40% 60%, #FFCC00 0%, transparent 65%),
radial-gradient(ellipse 2.5px 10px at 85% 50%, #DEB887 0%, transparent 70%),
radial-gradient(ellipse 1.8px 7px at 15% 80%, #DAA520 0%, transparent 68%);
background-size:
/* 主茎秆 */
100vw 80vh, 100vw 82vh,
/* 主穗头 */
50vw 60vh, 48vw 58vh, 52vw 62vh, 46vw 56vh, 50vw 60vh,
/* 分支 */
40vw 50vh, 38vw 48vh, 42vw 52vh, 36vw 46vh,
/* 左侧茎秆 */
100vw 70vh,
/* 左侧穗头 */
35vw 45vh, 33vw 43vh, 37vw 47vh,
/* 麦田远景 */
100vw 100vh,
/* 散落稻粒 */
20vw 20vh, 25vw 25vh, 30vw 30vh, 22vw 22vh;
background-position:
/* 主茎秆 */
70% 20%, 72% 18%,
/* 主穗头 */
60% 0%, 62% 2%, 58% -2%, 64% 4%, 60% 1%,
/* 分支 */
65% 15%, 67% 17%, 63% 13%, 69% 19%,
/* 左侧茎秆 */
20% 30%,
/* 左侧穗头 */
15% 20%, 17% 22%, 13% 18%,
/* 麦田远景 */
0% 0%,
/* 散落稻粒 */
30% 50%, 50% 70%, 80% 40%, 10% 80%;
background-repeat: no-repeat;
opacity: 0.4;
pointer-events: none;
z-index: -1;
animation: wheatSway 25s ease-in-out infinite;
}
@keyframes spiralRotate {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
/* 流星效果容器 */
.meteor-container {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: -1;
overflow: hidden;
}
/* 流星轨迹 */
.meteor {
position: absolute;
width: 2px;
height: 2px;
background: radial-gradient(circle, #FFD700 0%, #FFA500 50%, transparent 100%);
border-radius: 50%;
box-shadow:
0 0 10px #FFD700,
0 0 20px #FFA500,
0 0 30px #FF8C00;
animation: meteorFall linear infinite;
}
/* 流星尾迹 */
.meteor::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100px;
height: 1px;
background: linear-gradient(90deg,
#FFD700 0%,
#FFA500 30%,
#FF8C00 60%,
transparent 100%);
transform-origin: 0 50%;
transform: rotate(-45deg);
opacity: 0.8;
}
@keyframes meteorFall {
0% {
transform: translateX(-100px) translateY(-100px);
opacity: 0;
}
10% {
opacity: 1;
}
90% {
opacity: 1;
}
100% {
transform: translateX(calc(100vw + 100px)) translateY(calc(100vh + 100px));
opacity: 0;
}
}
/* 多个流星的不同轨迹 */
.meteor:nth-child(1) {
top: 10%;
left: -100px;
animation-duration: 8s;
animation-delay: 0s;
}
.meteor:nth-child(2) {
top: 20%;
left: -100px;
animation-duration: 12s;
animation-delay: 2s;
}
.meteor:nth-child(3) {
top: 30%;
left: -100px;
animation-duration: 10s;
animation-delay: 4s;
}
.meteor:nth-child(4) {
top: 50%;
left: -100px;
animation-duration: 15s;
animation-delay: 6s;
}
.meteor:nth-child(5) {
top: 70%;
left: -100px;
animation-duration: 9s;
animation-delay: 8s;
}
.meteor:nth-child(6) {
top: 80%;
left: -100px;
animation-duration: 11s;
animation-delay: 10s;
}
/* 金色粒子效果 */
.golden-particles {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: -1;
}
.particle {
position: absolute;
width: 3px;
height: 3px;
background: radial-gradient(circle, #FFD700 0%, #FFA500 70%, transparent 100%);
border-radius: 50%;
animation: particleFloat linear infinite;
}
@keyframes particleFloat {
0% {
transform: translateY(100vh) rotate(0deg);
opacity: 0;
}
10% {
opacity: 1;
}
90% {
opacity: 1;
}
100% {
transform: translateY(-100px) rotate(360deg);
opacity: 0;
}
}
/* 粒子的不同位置和动画时长 */
.particle:nth-child(1) { left: 10%; animation-duration: 20s; animation-delay: 0s; }
.particle:nth-child(2) { left: 20%; animation-duration: 25s; animation-delay: 2s; }
.particle:nth-child(3) { left: 30%; animation-duration: 18s; animation-delay: 4s; }
.particle:nth-child(4) { left: 40%; animation-duration: 22s; animation-delay: 6s; }
.particle:nth-child(5) { left: 50%; animation-duration: 24s; animation-delay: 8s; }
.particle:nth-child(6) { left: 60%; animation-duration: 19s; animation-delay: 10s; }
.particle:nth-child(7) { left: 70%; animation-duration: 21s; animation-delay: 12s; }
.particle:nth-child(8) { left: 80%; animation-duration: 23s; animation-delay: 14s; }
.particle:nth-child(9) { left: 90%; animation-duration: 26s; animation-delay: 16s; }
/* 响应式设计 */
@media (max-width: 768px) {
.meteor {
width: 1px;
height: 1px;
}
.meteor::before {
width: 50px;
}
.particle {
width: 2px;
height: 2px;
}
body::before {
background-size:
30px 30px, 25px 25px, 35px 35px, 28px 28px, 32px 32px,
40px 40px, 45px 45px;
}
body::after {
background-size: 200px 200px, 150px 150px, 100px 100px;
}
}
/* 麦穗飘舞特效 */
.wheat-floating {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 2;
overflow: hidden;
}
/* 移动设备性能优化 */
@media (max-width: 768px) {
.wheat-floating {
display: none;
}
.golden-particles {
display: none;
}
.meteor-container {
display: none;
}
.adaptive-overlay {
animation: none;
background: rgba(255, 255, 255, 0.1);
}
}
.wheat-particle {
position: absolute;
width: 8px;
height: 20px;
background: linear-gradient(180deg,
#FFD700 0%,
#DAA520 50%,
#B8860B 100%
);
border-radius: 50% 50% 50% 50% / 60% 60% 40% 40%;
opacity: 0.7;
animation: wheatFloat 15s linear infinite;
}
.wheat-particle::before {
content: '';
position: absolute;
top: -3px;
left: 50%;
transform: translateX(-50%);
width: 2px;
height: 8px;
background: #8B7355;
border-radius: 1px;
}
.wheat-particle::after {
content: '';
position: absolute;
top: 2px;
left: 1px;
width: 2px;
height: 4px;
background: #FFEC8C;
border-radius: 50%;
box-shadow:
3px 2px 0 #FFEC8C,
1px 6px 0 #FFEC8C,
4px 8px 0 #FFEC8C;
}
@keyframes wheatFloat {
0% {
transform: translateY(-100vh) translateX(0) rotate(0deg);
opacity: 0;
}
10% {
opacity: 0.7;
}
90% {
opacity: 0.7;
}
100% {
transform: translateY(100vh) translateX(50px) rotate(360deg);
opacity: 0;
}
}
/* 不同大小和速度的麦穗 */
.wheat-particle:nth-child(1) {
left: 10%;
animation-duration: 12s;
animation-delay: 0s;
transform: scale(0.8);
}
.wheat-particle:nth-child(2) {
left: 25%;
animation-duration: 18s;
animation-delay: 2s;
transform: scale(1.2);
}
.wheat-particle:nth-child(3) {
left: 40%;
animation-duration: 15s;
animation-delay: 4s;
transform: scale(0.9);
}
.wheat-particle:nth-child(4) {
left: 60%;
animation-duration: 20s;
animation-delay: 1s;
transform: scale(1.1);
}
.wheat-particle:nth-child(5) {
left: 75%;
animation-duration: 14s;
animation-delay: 3s;
transform: scale(0.7);
}
.wheat-particle:nth-child(6) {
left: 90%;
animation-duration: 16s;
animation-delay: 5s;
transform: scale(1.0);
}
.wheat-particle:nth-child(7) {
left: 5%;
animation-duration: 22s;
animation-delay: 6s;
transform: scale(0.6);
}
.wheat-particle:nth-child(8) {
left: 35%;
animation-duration: 13s;
animation-delay: 2.5s;
transform: scale(1.3);
}
/* 减少动画偏好设置 */
@media (prefers-reduced-motion: reduce) {
* {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
.meteor,
.particle {
display: none;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,109 @@
<!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">
<div class="header-icon">🏮</div>
<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

@@ -0,0 +1,481 @@
// 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 class="info-item">
<div class="item-icon">🗓️</div>
<div class="item-label">本周第几天</div>
<div class="item-value">第${lunarData.stats.week_of_month}周</div>
</div>
<div class="info-item">
<div class="item-icon">⏰</div>
<div class="item-label">今日进度</div>
<div class="item-value">${lunarData.stats.percents_formatted.day}</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: rgba(255, 255, 255, 0.95);
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

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

View File

@@ -0,0 +1,647 @@
{
"code": 200,
"message": "获取成功。数据来自官方/权威源头,以确保稳定与实时。开源地址 https://github.com/vikiboss/60s反馈群 595941841",
"data": {
"solar": {
"year": 2025,
"month": 9,
"day": 1,
"hour": 17,
"minute": 58,
"second": 47,
"full": "2025-09-01",
"full_with_time": "2025-09-01 17:58:47",
"week": 1,
"week_desc": "星期一",
"week_desc_short": "一",
"season": 3,
"season_desc": "三季度",
"season_desc_short": "三",
"season_name": "秋",
"season_name_desc": "秋天",
"is_leap_year": false
},
"lunar": {
"year": "乙巳",
"month": "七",
"day": "初十",
"hour": "酉",
"full_with_hour": "农历乙巳年七月初十酉时",
"desc_short": "农历乙巳年七月初十",
"year_desc": "农历乙巳年",
"month_desc": "七月",
"day_desc": "初十",
"hour_desc": "酉时",
"is_leap_month": false
},
"stats": {
"day_of_year": 244,
"week_of_year": 36,
"week_of_month": 1,
"percents": {
"year": 0.665753424657534,
"month": 0.0333333333333333,
"week": 0.142857142857143,
"day": 0.749161909722222
},
"percents_formatted": {
"year": "66.58%",
"month": "3.33%",
"week": "14.29%",
"day": "74.92%"
}
},
"term": {
"today": null,
"stage": {
"name": "处暑",
"position": 10,
"is_jie": false,
"is_qi": true
}
},
"zodiac": {
"year": "蛇",
"month": "鸡",
"day": "鸡",
"hour": "鸡"
},
"sixty_cycle": {
"year": {
"heaven_stem": "乙",
"earth_branch": "巳",
"name": "乙巳年",
"name_short": "乙巳"
},
"month": {
"heaven_stem": "乙",
"earth_branch": "酉",
"name": "乙酉月",
"name_short": "乙酉"
},
"day": {
"heaven_stem": "癸",
"earth_branch": "酉",
"name": "癸酉日",
"name_short": "癸酉"
},
"hour": {
"heaven_stem": "辛",
"earth_branch": "酉",
"name": "辛酉时",
"name_short": "辛酉"
}
},
"legal_holiday": null,
"festival": {
"solar": null,
"lunar": null,
"both_desc": null
},
"phase": {
"name": "宵月",
"position": 10
},
"constellation": {
"name": "处女座",
"name_short": "处女"
},
"taboo": {
"day": {
"recommends": "解除.祭祀.祈福.求嗣.修造.动土.竖柱.上梁.安床.纳畜.盖屋.合脊.起基.入殓.破土.安葬",
"avoids": "出火.嫁娶.开光.进人口.出行.词讼.开市.入宅.移徙.赴任"
},
"hour": {
"hour": "酉时",
"hour_short": "酉",
"avoids": "乘船.造桥",
"recommends": "嫁娶.出行.移徙.入宅.开市.赴任.祈福.安床.开仓.盖屋.修造.求财"
},
"hours": [
{
"hour": "酉时",
"hour_short": "酉",
"recommends": "嫁娶.出行.移徙.入宅.开市.赴任.祈福.安床.开仓.盖屋.修造.求财",
"avoids": "乘船.造桥"
},
{
"hour": "戌时",
"hour_short": "戌",
"recommends": "嫁娶.移徙.安葬.进人口.求财",
"avoids": "出行.赴任.祈福.祭祀.开光.斋醮"
},
{
"hour": "亥时",
"hour_short": "亥",
"recommends": "嫁娶.移徙.交易.入宅.开市.安葬.求嗣.求财",
"avoids": "出行.赴任.动土.祈福.祭祀.修造.开光.斋醮"
},
{
"hour": "子时",
"hour_short": "子",
"recommends": "嫁娶.交易.入宅.开市.祈福.安葬.求嗣.求财",
"avoids": "出行.移徙.赴任.词讼.修造"
},
{
"hour": "丑时",
"hour_short": "丑",
"recommends": "嫁娶.祈福.安葬.祭祀.酬神.求财",
"avoids": "出行.赴任.动土.修造"
},
{
"hour": "寅时",
"hour_short": "寅",
"recommends": "嫁娶.出行.交易.开市.赴任.祈福.安床.祭祀.求嗣.求财",
"avoids": "盖屋.入殓.上梁"
},
{
"hour": "卯时",
"hour_short": "卯",
"recommends": "嫁娶.交易.入宅.开市.祈福.安床.安葬.求嗣.求财",
"avoids": "出行.赴任.修造"
},
{
"hour": "辰时",
"hour_short": "辰",
"recommends": "",
"avoids": "诸事不宜"
},
{
"hour": "巳时",
"hour_short": "巳",
"recommends": "嫁娶.出行.移徙.入宅.开市.祈福.安床.盖屋.祭祀.作灶",
"avoids": "安葬.修造.开光"
},
{
"hour": "午时",
"hour_short": "午",
"recommends": "嫁娶.出行.交易.开市.祈福.安床.求嗣.求财",
"avoids": "赴任.动土.词讼.修造"
},
{
"hour": "未时",
"hour_short": "未",
"recommends": "嫁娶.入宅.祈福.安葬.祭祀.修造.酬神.求财",
"avoids": "出行.赴任"
},
{
"hour": "申时",
"hour_short": "申",
"recommends": "嫁娶.出行.开市.赴任.安葬.求财",
"avoids": "祈福.祭祀.酬神.斋醮"
}
]
},
"julian_day": 2460919.5,
"nayin": {
"year": "覆灯火",
"month": "泉中水",
"day": "剑锋金",
"hour": "石榴木"
},
"baizi": {
"year_baizi": "性格温和,为人正直诚信。",
"day_baizi": "性格温和,为人正直诚信。"
},
"fortune": {
"today_luck": "今日学习运好,适合进修",
"career": "领导能力突出,升职有望",
"money": "偏财运不错,可小试投资",
"love": "感情需要沟通,避免误会"
},
"constants": {
"legal_holiday_list": [
{
"name": "元旦节",
"date": "2025-01-01",
"start": "2025-01-01",
"end": "2025-01-01"
},
{
"name": "春节",
"date": "2025-01-29",
"start": "2025-01-26",
"end": "2025-02-08"
},
{
"name": "清明节",
"date": "2025-04-04",
"start": "2025-04-04",
"end": "2025-04-06"
},
{
"name": "劳动节",
"date": "2025-05-01",
"start": "2025-04-27",
"end": "2025-05-05"
},
{
"name": "端午节",
"date": "2025-05-31",
"start": "2025-05-31",
"end": "2025-06-02"
},
{
"name": "国庆中秋",
"date": "2025-10-01",
"start": "2025-09-28",
"end": "2025-10-11"
}
],
"phase_list": [
{
"name": "朔月",
"lunar_day": 1
},
{
"name": "既朔月",
"lunar_day": 2
},
{
"name": "蛾眉新月",
"lunar_day": 3
},
{
"name": "蛾眉新月",
"lunar_day": 4
},
{
"name": "蛾眉月",
"lunar_day": 5
},
{
"name": "夕月",
"lunar_day": 6
},
{
"name": "上弦月",
"lunar_day": 7
},
{
"name": "上弦月",
"lunar_day": 8
},
{
"name": "九夜月",
"lunar_day": 9
},
{
"name": "宵月",
"lunar_day": 10
},
{
"name": "宵月",
"lunar_day": 11
},
{
"name": "宵月",
"lunar_day": 12
},
{
"name": "渐盈凸月",
"lunar_day": 13
},
{
"name": "小望月",
"lunar_day": 14
},
{
"name": "望月",
"lunar_day": 15
},
{
"name": "既望月",
"lunar_day": 16
},
{
"name": "立待月",
"lunar_day": 17
},
{
"name": "居待月",
"lunar_day": 18
},
{
"name": "寝待月",
"lunar_day": 19
},
{
"name": "更待月",
"lunar_day": 20
},
{
"name": "渐亏凸月",
"lunar_day": 21
},
{
"name": "下弦月",
"lunar_day": 22
},
{
"name": "下弦月",
"lunar_day": 23
},
{
"name": "有明月",
"lunar_day": 24
},
{
"name": "有明月",
"lunar_day": 25
},
{
"name": "蛾眉残月",
"lunar_day": 26
},
{
"name": "蛾眉残月",
"lunar_day": 27
},
{
"name": "残月",
"lunar_day": 28
},
{
"name": "晓月",
"lunar_day": 29
},
{
"name": "晦月",
"lunar_day": 30
}
],
"zodiac_list": [
"鼠",
"牛",
"虎",
"兔",
"龙",
"蛇",
"马",
"羊",
"猴",
"鸡",
"狗",
"猪"
],
"constellation_list": [
{
"name": "白羊",
"desc": "白羊座",
"start": "3月21日",
"end": "4月19日",
"range": "3月21日~4月19日",
"start_month": 3,
"start_day": 21,
"end_month": 4,
"end_day": 19
},
{
"name": "金牛",
"desc": "金牛座",
"start": "4月20日",
"end": "5月20日",
"range": "4月20日~5月20日",
"start_month": 4,
"start_day": 20,
"end_month": 5,
"end_day": 20
},
{
"name": "双子",
"desc": "双子座",
"start": "5月21日",
"end": "6月21日",
"range": "5月21日~6月21日",
"start_month": 5,
"start_day": 21,
"end_month": 6,
"end_day": 21
},
{
"name": "巨蟹",
"desc": "巨蟹座",
"start": "6月22日",
"end": "7月22日",
"range": "6月22日~7月22日",
"start_month": 6,
"start_day": 22,
"end_month": 7,
"end_day": 22
},
{
"name": "狮子",
"desc": "狮子座",
"start": "7月23日",
"end": "8月22日",
"range": "7月23日~8月22日",
"start_month": 7,
"start_day": 23,
"end_month": 8,
"end_day": 22
},
{
"name": "处女",
"desc": "处女座",
"start": "8月23日",
"end": "9月22日",
"range": "8月23日~9月22日",
"start_month": 8,
"start_day": 23,
"end_month": 9,
"end_day": 22
},
{
"name": "天秤",
"desc": "天秤座",
"start": "9月23日",
"end": "10月23日",
"range": "9月23日~10月23日",
"start_month": 9,
"start_day": 23,
"end_month": 10,
"end_day": 23
},
{
"name": "天蝎",
"desc": "天蝎座",
"start": "10月24日",
"end": "11月22日",
"range": "10月24日~11月22日",
"start_month": 10,
"start_day": 24,
"end_month": 11,
"end_day": 22
},
{
"name": "射手",
"desc": "射手座",
"start": "11月23日",
"end": "12月21日",
"range": "11月23日~12月21日",
"start_month": 11,
"start_day": 23,
"end_month": 12,
"end_day": 21
},
{
"name": "摩羯",
"desc": "摩羯座",
"start": "12月22日",
"end": "1月19日",
"range": "12月22日~1月19日",
"start_month": 12,
"start_day": 22,
"end_month": 1,
"end_day": 19
},
{
"name": "水瓶",
"desc": "水瓶座",
"start": "1月20日",
"end": "2月18日",
"range": "1月20日~2月18日",
"start_month": 1,
"start_day": 20,
"end_month": 2,
"end_day": 18
},
{
"name": "双鱼",
"desc": "双鱼座",
"start": "2月19日",
"end": "3月20日",
"range": "2月19日~3月20日",
"start_month": 2,
"start_day": 19,
"end_month": 3,
"end_day": 20
}
],
"heaven_stems": [
"甲",
"乙",
"丙",
"丁",
"戊",
"己",
"庚",
"辛",
"壬",
"癸"
],
"earth_branches": [
"子",
"丑",
"寅",
"卯",
"辰",
"巳",
"午",
"未",
"申",
"酉",
"戌",
"亥"
],
"solar_terms": [
{
"name": "立春",
"desc": "春季开始"
},
{
"name": "雨水",
"desc": "降雨增多"
},
{
"name": "惊蛰",
"desc": "春雷乍响"
},
{
"name": "春分",
"desc": "昼夜等长"
},
{
"name": "清明",
"desc": "天清地明"
},
{
"name": "谷雨",
"desc": "雨生百谷"
},
{
"name": "立夏",
"desc": "夏季开始"
},
{
"name": "小满",
"desc": "麦粒渐满"
},
{
"name": "芒种",
"desc": "麦类收割"
},
{
"name": "夏至",
"desc": "白昼最长"
},
{
"name": "小暑",
"desc": "天气渐热"
},
{
"name": "大暑",
"desc": "一年最热"
},
{
"name": "立秋",
"desc": "秋季开始"
},
{
"name": "处暑",
"desc": "暑热结束"
},
{
"name": "白露",
"desc": "露水增多"
},
{
"name": "秋分",
"desc": "昼夜等长"
},
{
"name": "寒露",
"desc": "露水渐凉"
},
{
"name": "霜降",
"desc": "开始降霜"
},
{
"name": "立冬",
"desc": "冬季开始"
},
{
"name": "小雪",
"desc": "开始降雪"
},
{
"name": "大雪",
"desc": "降雪增多"
},
{
"name": "冬至",
"desc": "白昼最短"
},
{
"name": "小寒",
"desc": "天气渐冷"
},
{
"name": "大寒",
"desc": "一年最冷"
}
]
}
}
}

View File

@@ -0,0 +1,561 @@
/* Reset and Base Styles */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
color: #333;
overflow-x: hidden;
}
.container {
max-width: 1400px;
margin: 0 auto;
padding: 20px;
position: relative;
}
/* Header Styles */
.header {
text-align: center;
margin-bottom: 40px;
position: relative;
z-index: 2;
}
.header-content {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(20px);
border-radius: 24px;
padding: 40px;
border: 1px solid rgba(255, 255, 255, 0.2);
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
position: relative;
overflow: hidden;
}
.header-content::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(45deg, rgba(255, 255, 255, 0.1) 0%, transparent 50%, rgba(255, 255, 255, 0.1) 100%);
animation: shimmer 3s ease-in-out infinite;
}
@keyframes shimmer {
0%, 100% { transform: translateX(-100%); }
50% { transform: translateX(100%); }
}
.logo {
display: flex;
align-items: center;
justify-content: center;
gap: 15px;
margin-bottom: 15px;
}
.logo i {
font-size: 48px;
background: linear-gradient(45deg, #ff6b6b, #4ecdc4);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
animation: pulse 2s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.1); }
}
.logo h1 {
font-size: 42px;
font-weight: 700;
color: white;
text-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
}
.subtitle {
font-size: 18px;
color: rgba(255, 255, 255, 0.9);
font-weight: 400;
letter-spacing: 0.5px;
}
/* Floating Shapes */
.header-decoration {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
pointer-events: none;
z-index: 1;
}
.floating-shapes {
position: relative;
width: 100%;
height: 100%;
}
.shape {
position: absolute;
border-radius: 50%;
background: linear-gradient(45deg, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0.05));
animation: float 6s ease-in-out infinite;
}
.shape-1 {
width: 80px;
height: 80px;
top: 10%;
left: 10%;
animation-delay: 0s;
}
.shape-2 {
width: 60px;
height: 60px;
top: 20%;
right: 15%;
animation-delay: 2s;
}
.shape-3 {
width: 100px;
height: 100px;
bottom: 15%;
left: 20%;
animation-delay: 4s;
}
@keyframes float {
0%, 100% { transform: translateY(0px) rotate(0deg); }
33% { transform: translateY(-20px) rotate(120deg); }
66% { transform: translateY(10px) rotate(240deg); }
}
/* Input Section */
.input-section {
margin-bottom: 40px;
}
.input-card {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20px);
border-radius: 20px;
padding: 30px;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
border: 1px solid rgba(255, 255, 255, 0.3);
transition: all 0.3s ease;
}
.input-card:hover {
transform: translateY(-5px);
box-shadow: 0 30px 60px rgba(0, 0, 0, 0.15);
}
.card-header {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 20px;
}
.card-header i {
font-size: 24px;
color: #667eea;
}
.card-header h2 {
font-size: 24px;
font-weight: 600;
color: #333;
}
.input-wrapper {
position: relative;
}
#inputText {
width: 100%;
padding: 20px;
border: 2px solid #e1e5e9;
border-radius: 12px;
font-size: 16px;
font-family: inherit;
resize: vertical;
transition: all 0.3s ease;
background: rgba(255, 255, 255, 0.8);
backdrop-filter: blur(10px);
}
#inputText:focus {
outline: none;
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
background: rgba(255, 255, 255, 0.95);
}
.input-actions {
display: flex;
gap: 15px;
margin-top: 20px;
justify-content: flex-end;
}
/* Button Styles */
.btn {
padding: 12px 24px;
border: none;
border-radius: 10px;
font-size: 16px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
gap: 8px;
text-decoration: none;
position: relative;
overflow: hidden;
}
.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;
}
.btn:hover::before {
left: 100%;
}
.btn-primary {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3);
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(102, 126, 234, 0.4);
}
.btn-secondary {
background: rgba(255, 255, 255, 0.8);
color: #666;
border: 1px solid #e1e5e9;
}
.btn-secondary:hover {
background: rgba(255, 255, 255, 0.95);
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
}
/* Results Section */
.results-section {
opacity: 0;
transform: translateY(30px);
transition: all 0.5s ease;
}
.results-section.show {
opacity: 1;
transform: translateY(0);
}
.results-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
gap: 30px;
}
.result-card {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20px);
border-radius: 20px;
padding: 30px;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
border: 1px solid rgba(255, 255, 255, 0.3);
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.result-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4px;
background: linear-gradient(90deg, #ff6b6b, #4ecdc4, #45b7d1, #96ceb4);
}
.result-card:hover {
transform: translateY(-5px);
box-shadow: 0 30px 60px rgba(0, 0, 0, 0.15);
}
.result-card .card-header h3 {
font-size: 20px;
font-weight: 600;
color: #333;
}
.result-items {
display: flex;
flex-direction: column;
gap: 20px;
}
.result-item {
position: relative;
}
.result-item label {
display: block;
font-size: 14px;
font-weight: 500;
color: #666;
margin-bottom: 8px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.result-value {
display: flex;
align-items: center;
background: rgba(248, 250, 252, 0.8);
border: 1px solid #e1e5e9;
border-radius: 8px;
padding: 12px 16px;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
font-size: 14px;
word-break: break-all;
position: relative;
transition: all 0.3s ease;
}
.result-value:hover {
background: rgba(248, 250, 252, 0.95);
border-color: #667eea;
}
.result-value .placeholder {
color: #999;
font-style: italic;
}
.copy-btn {
background: none;
border: none;
color: #667eea;
cursor: pointer;
padding: 8px;
border-radius: 6px;
transition: all 0.3s ease;
margin-left: auto;
flex-shrink: 0;
}
.copy-btn:hover {
background: rgba(102, 126, 234, 0.1);
color: #5a67d8;
}
/* Loading Overlay */
.loading-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
backdrop-filter: blur(5px);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
opacity: 0;
visibility: hidden;
transition: all 0.3s ease;
}
.loading-overlay.show {
opacity: 1;
visibility: visible;
}
.loading-spinner {
text-align: center;
color: white;
}
.spinner {
width: 50px;
height: 50px;
border: 4px solid rgba(255, 255, 255, 0.3);
border-top: 4px solid white;
border-radius: 50%;
animation: spin 1s linear infinite;
margin: 0 auto 20px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* Toast Notification */
.toast {
position: fixed;
bottom: 30px;
right: 30px;
background: linear-gradient(135deg, #4ecdc4, #44a08d);
color: white;
padding: 16px 24px;
border-radius: 12px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
display: flex;
align-items: center;
gap: 10px;
transform: translateX(400px);
transition: all 0.3s ease;
z-index: 1001;
}
.toast.show {
transform: translateX(0);
}
.toast i {
font-size: 18px;
}
/* Responsive Design */
@media (max-width: 768px) {
.container {
padding: 15px;
}
.header-content {
padding: 30px 20px;
}
.logo h1 {
font-size: 32px;
}
.logo i {
font-size: 36px;
}
.results-grid {
grid-template-columns: 1fr;
gap: 20px;
}
.input-actions {
flex-direction: column;
}
.btn {
justify-content: center;
}
.toast {
bottom: 20px;
right: 20px;
left: 20px;
transform: translateY(100px);
}
.toast.show {
transform: translateY(0);
}
}
@media (max-width: 480px) {
.input-card,
.result-card {
padding: 20px;
}
.card-header h2,
.card-header h3 {
font-size: 18px;
}
.result-value {
font-size: 12px;
padding: 10px 12px;
}
}
/* Animation Classes */
.fade-in {
animation: fadeIn 0.5s ease-in-out;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.slide-in {
animation: slideIn 0.3s ease-out;
}
@keyframes slideIn {
from {
transform: translateX(-20px);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
/* Custom Scrollbar */
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: rgba(255, 255, 255, 0.1);
border-radius: 4px;
}
::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.3);
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: rgba(255, 255, 255, 0.5);
}

View File

@@ -0,0 +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>
</html>

View File

@@ -0,0 +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
};
}

View File

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

View File

@@ -0,0 +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": ""
}
}
}
API encoded/decoded encode/decode

View File

@@ -0,0 +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);
}
}

View File

@@ -0,0 +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>
</html>

View File

@@ -0,0 +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);
});

View File

@@ -0,0 +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;
}
}

View File

@@ -0,0 +1,551 @@
{
"code": 200,
"message": "获取成功。数据来自官方/权威源头,以确保稳定与实时。开源地址 https://github.com/vikiboss/60s反馈群 595941841",
"data": [
{
"code": "sq",
"label": "阿尔巴尼亚语",
"alphabet": "A"
},
{
"code": "ga",
"label": "爱尔兰语",
"alphabet": "A"
},
{
"code": "et",
"label": "爱沙尼亚语",
"alphabet": "A"
},
{
"code": "ar",
"label": "阿拉伯语",
"alphabet": "A"
},
{
"code": "am",
"label": "阿姆哈拉语",
"alphabet": "A"
},
{
"code": "az",
"label": "阿塞拜疆语",
"alphabet": "A"
},
{
"code": "be",
"label": "白俄罗斯语",
"alphabet": "B"
},
{
"code": "bg",
"label": "保加利亚语",
"alphabet": "B"
},
{
"code": "eu",
"label": "巴斯克语",
"alphabet": "B"
},
{
"code": "is",
"label": "冰岛语",
"alphabet": "B"
},
{
"code": "pl",
"label": "波兰语",
"alphabet": "B"
},
{
"code": "bs-Latn",
"label": "波斯尼亚语(拉丁语)",
"alphabet": "B"
},
{
"code": "fa",
"label": "波斯语",
"alphabet": "B"
},
{
"code": "da",
"label": "丹麦语",
"alphabet": "D"
},
{
"code": "de",
"label": "德语",
"alphabet": "D"
},
{
"code": "ru",
"label": "俄语",
"alphabet": "E"
},
{
"code": "fr",
"label": "法语",
"alphabet": "F"
},
{
"code": "tl",
"label": "菲律宾语",
"alphabet": "F"
},
{
"code": "fi",
"label": "芬兰语",
"alphabet": "F"
},
{
"code": "fy",
"label": "弗里斯兰语",
"alphabet": "F"
},
{
"code": "km",
"label": "高棉语",
"alphabet": "G"
},
{
"code": "ka",
"label": "格鲁吉亚语",
"alphabet": "G"
},
{
"code": "gu",
"label": "古吉拉特语",
"alphabet": "G"
},
{
"code": "ko",
"label": "韩语",
"alphabet": "H"
},
{
"code": "ht",
"label": "海地语",
"alphabet": "H"
},
{
"code": "ha",
"label": "豪萨语",
"alphabet": "H"
},
{
"code": "kk",
"label": "哈萨克语",
"alphabet": "H"
},
{
"code": "nl",
"label": "荷兰语",
"alphabet": "H"
},
{
"code": "gl",
"label": "加利西亚语",
"alphabet": "J"
},
{
"code": "ca",
"label": "加泰罗尼亚语",
"alphabet": "J"
},
{
"code": "cs",
"label": "捷克语",
"alphabet": "J"
},
{
"code": "ky",
"label": "吉尔吉斯斯坦语",
"alphabet": "J"
},
{
"code": "kn",
"label": "卡纳达语",
"alphabet": "K"
},
{
"code": "tlh",
"label": "克林贡语",
"alphabet": "K"
},
{
"code": "hr",
"label": "克罗地亚语",
"alphabet": "K"
},
{
"code": "otq",
"label": "克洛塔罗乙巳语",
"alphabet": "K"
},
{
"code": "co",
"label": "科西嘉语",
"alphabet": "K"
},
{
"code": "ku",
"label": "库尔德语",
"alphabet": "K"
},
{
"code": "la",
"label": "拉丁语",
"alphabet": "L"
},
{
"code": "lo",
"label": "老挝语",
"alphabet": "L"
},
{
"code": "lv",
"label": "拉脱维亚语",
"alphabet": "L"
},
{
"code": "lt",
"label": "立陶宛语",
"alphabet": "L"
},
{
"code": "ro",
"label": "罗马尼亚语",
"alphabet": "L"
},
{
"code": "lb",
"label": "卢森堡语",
"alphabet": "L"
},
{
"code": "mg",
"label": "马尔加什语",
"alphabet": "M"
},
{
"code": "mt",
"label": "马耳他语",
"alphabet": "M"
},
{
"code": "mr",
"label": "马拉地语",
"alphabet": "M"
},
{
"code": "ms",
"label": "马来语",
"alphabet": "M"
},
{
"code": "ml",
"label": "马拉雅拉姆语",
"alphabet": "M"
},
{
"code": "mi",
"label": "毛利语",
"alphabet": "M"
},
{
"code": "mk",
"label": "马其顿语",
"alphabet": "M"
},
{
"code": "mn",
"label": "蒙古语",
"alphabet": "M"
},
{
"code": "bn",
"label": "孟加拉语",
"alphabet": "M"
},
{
"code": "my",
"label": "缅甸语",
"alphabet": "M"
},
{
"code": "mww",
"label": "苗族昂山土语",
"alphabet": "M"
},
{
"code": "hmn",
"label": "苗族语",
"alphabet": "M"
},
{
"code": "xh",
"label": "南非科萨语",
"alphabet": "N"
},
{
"code": "zu",
"label": "南非祖鲁语",
"alphabet": "N"
},
{
"code": "ne",
"label": "尼泊尔语",
"alphabet": "N"
},
{
"code": "no",
"label": "挪威语",
"alphabet": "N"
},
{
"code": "pa",
"label": "旁遮普语",
"alphabet": "P"
},
{
"code": "ps",
"label": "普什图语",
"alphabet": "P"
},
{
"code": "pt",
"label": "葡萄牙语",
"alphabet": "P"
},
{
"code": "ny",
"label": "齐切瓦语",
"alphabet": "Q"
},
{
"code": "ja",
"label": "日语",
"alphabet": "R"
},
{
"code": "sv",
"label": "瑞典语",
"alphabet": "R"
},
{
"code": "sr-Latn",
"label": "塞尔维亚语(拉丁语)",
"alphabet": "S"
},
{
"code": "sr-Cyrl",
"label": "塞尔维亚语(西里尔)",
"alphabet": "S"
},
{
"code": "st",
"label": "塞索托语",
"alphabet": "S"
},
{
"code": "sm",
"label": "萨摩亚语",
"alphabet": "S"
},
{
"code": "si",
"label": "僧伽罗语",
"alphabet": "S"
},
{
"code": "eo",
"label": "世界语",
"alphabet": "S"
},
{
"code": "sk",
"label": "斯洛伐克语",
"alphabet": "S"
},
{
"code": "sl",
"label": "斯洛语尼亚语",
"alphabet": "S"
},
{
"code": "sw",
"label": "斯瓦希里语",
"alphabet": "S"
},
{
"code": "gd",
"label": "苏格兰盖尔语",
"alphabet": "S"
},
{
"code": "so",
"label": "索马里语",
"alphabet": "S"
},
{
"code": "ceb",
"label": "宿务语",
"alphabet": "S"
},
{
"code": "te",
"label": "泰卢固语",
"alphabet": "T"
},
{
"code": "ta",
"label": "泰米尔语",
"alphabet": "T"
},
{
"code": "th",
"label": "泰语",
"alphabet": "T"
},
{
"code": "tg",
"label": "塔吉克语",
"alphabet": "T"
},
{
"code": "tr",
"label": "土耳其语",
"alphabet": "T"
},
{
"code": "cy",
"label": "威尔士语",
"alphabet": "W"
},
{
"code": "zh-lzh",
"label": "文言文",
"alphabet": "W"
},
{
"code": "ur",
"label": "乌尔都语",
"alphabet": "W"
},
{
"code": "uk",
"label": "乌克兰语",
"alphabet": "W"
},
{
"code": "uz",
"label": "乌兹别克语",
"alphabet": "W"
},
{
"code": "haw",
"label": "夏威夷语",
"alphabet": "X"
},
{
"code": "es",
"label": "西班牙语",
"alphabet": "X"
},
{
"code": "he",
"label": "希伯来语",
"alphabet": "X"
},
{
"code": "el",
"label": "希腊语",
"alphabet": "X"
},
{
"code": "sd",
"label": "信德语",
"alphabet": "X"
},
{
"code": "hu",
"label": "匈牙利语",
"alphabet": "X"
},
{
"code": "sn",
"label": "修纳语",
"alphabet": "X"
},
{
"code": "en",
"label": "英语",
"alphabet": "Y"
},
{
"code": "hy",
"label": "亚美尼亚语",
"alphabet": "Y"
},
{
"code": "ig",
"label": "伊博语",
"alphabet": "Y"
},
{
"code": "it",
"label": "意大利语",
"alphabet": "Y"
},
{
"code": "yi",
"label": "意第绪语",
"alphabet": "Y"
},
{
"code": "hi",
"label": "印地语",
"alphabet": "Y"
},
{
"code": "id",
"label": "印度尼西亚语",
"alphabet": "Y"
},
{
"code": "su",
"label": "印尼巽他语",
"alphabet": "Y"
},
{
"code": "jw",
"label": "印尼爪哇语",
"alphabet": "Y"
},
{
"code": "yua",
"label": "尤卡坦玛雅语",
"alphabet": "Y"
},
{
"code": "yo",
"label": "约鲁巴语",
"alphabet": "Y"
},
{
"code": "vi",
"label": "越南语",
"alphabet": "Y"
},
{
"code": "zh-CHS",
"label": "中文",
"alphabet": "Z"
},
{
"code": "zh-CHT",
"label": "中文(繁体)",
"alphabet": "Z"
}
]
}

View File

@@ -0,0 +1 @@
{"code":200,"message":"所有数据均来自官方,确保稳定与实时,用户群: 595941841开源地址: https://github.com/vikiboss/60s","data":{"source":{"text":"こんにちは","type":"ja","type_desc":"日语","pronounce":"Konnitiha"},"target":{"text":"你好","type":"zh-CHS","type_desc":"中文","pronounce":"nĭhăo"}}}

View File

@@ -0,0 +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;
}
}

View File

@@ -0,0 +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);
}
}

View File

@@ -0,0 +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>
</html>

View File

@@ -0,0 +1,211 @@
// 天气查询应用
class WeatherApp {
constructor() {
this.apiEndpoints = [
"https://60s.api.shumengya.top/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, forecast } = data;
// 显示位置信息
document.getElementById('locationName').textContent = location.formatted;
document.getElementById('locationDetail').textContent =
`${location.province} ${location.city} | 邮编: ${location.zip_code}`;
// 使用第一天的预报数据作为当前天气(今天的天气)
const todayWeather = forecast[0];
// 显示当前天气(使用今天的最高温度)
document.getElementById('temperature').textContent = todayWeather.temperature_high;
document.getElementById('weatherCondition').textContent =
`${todayWeather.weather_day}${todayWeather.weather_night}`;
// 体感温度(使用温度范围)
document.getElementById('feelsLike').textContent =
`温度范围 ${todayWeather.temperature_low}°C - ${todayWeather.temperature_high}°C`;
// 显示更新时间(使用当前时间)
document.getElementById('updateTime').textContent =
`${this.formatDate(new Date())} (基于预报数据)`;
// 显示天气预报
this.displayForecast(forecast);
this.showWeatherContainer();
}
displayForecast(forecast) {
const forecastGrid = document.getElementById('forecastGrid');
forecastGrid.innerHTML = '';
forecast.forEach((day, index) => {
const forecastItem = document.createElement('div');
forecastItem.className = 'forecast-item';
forecastItem.innerHTML = `
<div class="forecast-date">${day.date_desc}</div>
<div class="forecast-weather">
<div class="weather-day">${day.weather_day}</div>
<div class="weather-night">${day.weather_night}</div>
</div>
<div class="forecast-temp">
<span class="temp-high">${day.temperature_high}°</span>
<span class="temp-low">${day.temperature_low}°</span>
</div>
<div class="forecast-wind">
<div>${day.wind_direction_day} ${day.wind_strength_day}</div>
</div>
<div class="forecast-humidity">湿度: ${day.humidity}%</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}`;
}
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

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

View File

@@ -0,0 +1,101 @@
{
"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"
},
"forecast": [
{
"date": "9/4",
"date_desc": "今天",
"weather_day": "多云",
"weather_night": "阴",
"weather_code_day": "01",
"weather_code_night": "02",
"temperature_high": 31,
"temperature_low": 21,
"wind_direction_day": "南风",
"wind_direction_night": "南风",
"wind_strength_day": "\u003C3级",
"wind_strength_night": "\u003C3级",
"rainfall": 96.1,
"humidity": 83
},
{
"date": "9/5",
"date_desc": "星期五",
"weather_day": "中雨",
"weather_night": "多云",
"weather_code_day": "08",
"weather_code_night": "01",
"temperature_high": 23,
"temperature_low": 19,
"wind_direction_day": "西南风",
"wind_direction_night": "北风",
"wind_strength_day": "\u003C3级",
"wind_strength_night": "\u003C3级",
"rainfall": 100,
"humidity": 68
},
{
"date": "9/6",
"date_desc": "星期六",
"weather_day": "多云",
"weather_night": "晴",
"weather_code_day": "01",
"weather_code_night": "00",
"temperature_high": 30,
"temperature_low": 19,
"wind_direction_day": "南风",
"wind_direction_night": "西南风",
"wind_strength_day": "\u003C3级",
"wind_strength_night": "\u003C3级",
"rainfall": 85.2,
"humidity": 36
},
{
"date": "9/7",
"date_desc": "星期日",
"weather_day": "多云",
"weather_night": "晴",
"weather_code_day": "01",
"weather_code_night": "00",
"temperature_high": 29,
"temperature_low": 20,
"wind_direction_day": "北风",
"wind_direction_night": "北风",
"wind_strength_day": "\u003C3级",
"wind_strength_night": "\u003C3级",
"rainfall": 87.3,
"humidity": 27
},
{
"date": "9/8",
"date_desc": "星期一",
"weather_day": "多云",
"weather_night": "多云",
"weather_code_day": "01",
"weather_code_night": "01",
"temperature_high": 28,
"temperature_low": 20,
"wind_direction_day": "南风",
"wind_direction_night": "南风",
"wind_strength_day": "\u003C3级",
"wind_strength_night": "\u003C3级",
"rainfall": 84.8,
"humidity": 41
}
]
}
}

View File

@@ -0,0 +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);
}
}

View File

@@ -0,0 +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>
</html>

View File

@@ -0,0 +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');
});

View File

@@ -0,0 +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;
}
}

View File

@@ -0,0 +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": "有降水,推荐您在室内进行休闲运动。"
}
}
}
}
}

View File

@@ -0,0 +1,878 @@
/* 基础样式重置 */
* {
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: #2c3e50;
min-height: 100vh;
overflow-x: hidden;
}
/* 容器布局 */
.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: 40px 20px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 20px;
box-shadow: 0 10px 30px rgba(102, 126, 234, 0.3);
color: white;
}
.header h1 {
font-size: 2.8rem;
font-weight: 700;
margin-bottom: 15px;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
}
.subtitle {
font-size: 1.2rem;
opacity: 0.9;
font-weight: 400;
}
/* 主内容区域 */
.main-content {
flex: 1;
display: flex;
flex-direction: column;
gap: 30px;
}
/* 输入容器 */
.input-container {
background: #ffffff;
border-radius: 20px;
padding: 40px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1);
border: 1px solid #e8ecf4;
}
.input-group {
margin-bottom: 30px;
}
.input-label {
display: block;
font-size: 1.1rem;
font-weight: 600;
color: #2c3e50;
margin-bottom: 15px;
}
.password-input-wrapper {
position: relative;
margin-bottom: 15px;
}
.password-input {
width: 100%;
padding: 18px 60px 18px 20px;
border: 2px solid #e8ecf4;
border-radius: 12px;
font-size: 1.1rem;
font-family: 'Courier New', monospace;
background: #f8fafc;
transition: all 0.3s ease;
letter-spacing: 1px;
}
.password-input:focus {
outline: none;
border-color: #667eea;
background: #ffffff;
box-shadow: 0 0 0 4px rgba(102, 126, 234, 0.1);
}
.password-input::placeholder {
color: #94a3b8;
letter-spacing: normal;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
}
.toggle-visibility {
position: absolute;
right: 15px;
top: 50%;
transform: translateY(-50%);
background: none;
border: none;
cursor: pointer;
padding: 8px;
border-radius: 6px;
color: #64748b;
transition: all 0.3s ease;
}
.toggle-visibility:hover {
background: #f1f5f9;
color: #475569;
}
.input-hint {
display: flex;
align-items: center;
gap: 8px;
color: #64748b;
font-size: 0.9rem;
}
.hint-icon {
font-size: 1rem;
}
/* 检测按钮 */
.check-btn {
width: 100%;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
padding: 18px 32px;
border-radius: 12px;
font-size: 1.1rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 4px 20px rgba(102, 126, 234, 0.3);
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
position: relative;
overflow: hidden;
}
.check-btn:hover {
transform: translateY(-2px);
box-shadow: 0 6px 25px rgba(102, 126, 234, 0.4);
}
.check-btn:active {
transform: translateY(0);
}
.check-btn:disabled {
opacity: 0.7;
cursor: not-allowed;
transform: none;
}
.btn-icon {
font-size: 1.2rem;
}
/* 结果容器 */
.result-container {
background: #ffffff;
border-radius: 20px;
padding: 40px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1);
border: 1px solid #e8ecf4;
animation: slideIn 0.5s ease-out;
}
@keyframes slideIn {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* 强度概览 */
.strength-overview {
margin-bottom: 40px;
padding: 30px;
background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
border-radius: 16px;
border: 1px solid #e2e8f0;
}
.strength-score {
display: flex;
align-items: center;
gap: 30px;
margin-bottom: 25px;
}
.score-circle {
width: 120px;
height: 120px;
border-radius: 50%;
background: conic-gradient(from 0deg, #e2e8f0 0deg, #e2e8f0 360deg);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
position: relative;
transition: all 0.5s ease;
}
.score-circle::before {
content: '';
position: absolute;
width: 90px;
height: 90px;
background: white;
border-radius: 50%;
z-index: 1;
}
.score-value {
font-size: 2.5rem;
font-weight: 700;
color: #2c3e50;
z-index: 2;
position: relative;
}
.score-label {
font-size: 0.9rem;
color: #64748b;
z-index: 2;
position: relative;
}
.strength-info {
flex: 1;
}
.strength-level {
font-size: 2rem;
font-weight: 700;
margin-bottom: 8px;
color: #2c3e50;
}
.strength-description {
font-size: 1.1rem;
color: #64748b;
line-height: 1.5;
}
.strength-bar {
margin-top: 20px;
}
.bar-background {
width: 100%;
height: 12px;
background: #e2e8f0;
border-radius: 6px;
overflow: hidden;
margin-bottom: 10px;
}
.bar-fill {
height: 100%;
background: linear-gradient(90deg, #ef4444, #f97316, #eab308, #22c55e);
border-radius: 6px;
width: 0%;
transition: width 0.8s ease;
}
.bar-labels {
display: flex;
justify-content: space-between;
font-size: 0.85rem;
color: #64748b;
}
/* 详细信息网格 */
.details-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 25px;
margin-bottom: 30px;
}
.detail-card {
background: #f8fafc;
border-radius: 16px;
padding: 25px;
border: 1px solid #e2e8f0;
transition: all 0.3s ease;
}
.detail-card:hover {
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
}
.card-header {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 20px;
}
.card-icon {
font-size: 1.5rem;
}
.card-header h3 {
font-size: 1.3rem;
font-weight: 600;
color: #2c3e50;
}
.card-content {
display: flex;
flex-direction: column;
gap: 15px;
}
.info-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 0;
border-bottom: 1px solid #e2e8f0;
}
.info-row:last-child {
border-bottom: none;
}
.info-label {
font-weight: 500;
color: #64748b;
}
.info-value {
font-weight: 600;
color: #2c3e50;
}
/* 字符类型分析 */
.character-types {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 12px;
margin-bottom: 20px;
}
.char-type {
display: flex;
align-items: center;
gap: 8px;
padding: 10px 12px;
background: white;
border-radius: 8px;
border: 1px solid #e2e8f0;
font-size: 0.9rem;
}
.char-type.has-type {
background: #dcfce7;
border-color: #bbf7d0;
color: #166534;
}
.char-type.has-type .type-icon {
color: #22c55e;
}
.type-icon {
font-size: 1rem;
}
.character-issues {
display: flex;
flex-direction: column;
gap: 8px;
}
.issue-item {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 12px;
background: #fef2f2;
border: 1px solid #fecaca;
border-radius: 8px;
color: #dc2626;
font-size: 0.9rem;
}
.issue-item.hidden {
display: none;
}
.issue-icon {
font-size: 1rem;
}
/* 建议和提示区域 */
.recommendations-section {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
gap: 25px;
}
.recommendations-card,
.security-tips-card {
background: #f8fafc;
border-radius: 16px;
padding: 25px;
border: 1px solid #e2e8f0;
}
.recommendations-list {
list-style: none;
display: flex;
flex-direction: column;
gap: 12px;
}
.recommendations-list li {
display: flex;
align-items: flex-start;
gap: 10px;
padding: 12px 16px;
background: white;
border-radius: 10px;
border: 1px solid #e2e8f0;
color: #2c3e50;
line-height: 1.5;
}
.recommendations-list li::before {
content: '💡';
font-size: 1rem;
margin-top: 2px;
flex-shrink: 0;
}
.tips-container {
display: flex;
flex-direction: column;
gap: 12px;
}
.tip-item {
display: flex;
align-items: flex-start;
gap: 12px;
padding: 12px 16px;
background: white;
border-radius: 10px;
border: 1px solid #e2e8f0;
color: #2c3e50;
line-height: 1.5;
}
.tip-icon {
font-size: 1rem;
margin-top: 2px;
flex-shrink: 0;
}
/* 错误容器 */
.error-container {
background: #ffffff;
border-radius: 20px;
padding: 50px 40px;
text-align: center;
box-shadow: 0 10px 40px rgba(239, 68, 68, 0.1);
border: 1px solid #fecaca;
}
.error-icon {
font-size: 4rem;
margin-bottom: 20px;
}
.error-container h3 {
color: #dc2626;
margin-bottom: 15px;
font-size: 1.5rem;
font-weight: 600;
}
.error-container p {
color: #64748b;
margin-bottom: 25px;
font-size: 1.1rem;
}
.retry-btn {
background: #dc2626;
color: white;
border: none;
padding: 14px 28px;
border-radius: 10px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
font-size: 1rem;
}
.retry-btn:hover {
background: #b91c1c;
transform: translateY(-1px);
}
/* 页脚 */
.footer {
text-align: center;
padding: 40px 20px;
color: #64748b;
margin-top: 40px;
}
.footer p {
margin-bottom: 8px;
font-size: 1rem;
}
.footer-note {
font-size: 0.9rem;
opacity: 0.8;
}
/* 提示框 */
.toast {
position: fixed;
top: 20px;
right: 20px;
background: #22c55e;
color: white;
padding: 16px 24px;
border-radius: 10px;
box-shadow: 0 4px 20px rgba(34, 197, 94, 0.3);
z-index: 1000;
animation: toastSlide 0.3s ease-out;
font-weight: 500;
}
@keyframes toastSlide {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
/* 强度等级颜色 */
.strength-weak {
color: #dc2626 !important;
}
.strength-medium {
color: #f59e0b !important;
}
.strength-strong {
color: #059669 !important;
}
.strength-very-strong {
color: #047857 !important;
}
/* 分数圆圈颜色 */
.score-weak {
background: conic-gradient(from 0deg, #dc2626 0deg, #dc2626 var(--score-deg), #e2e8f0 var(--score-deg), #e2e8f0 360deg) !important;
}
.score-medium {
background: conic-gradient(from 0deg, #f59e0b 0deg, #f59e0b var(--score-deg), #e2e8f0 var(--score-deg), #e2e8f0 360deg) !important;
}
.score-strong {
background: conic-gradient(from 0deg, #059669 0deg, #059669 var(--score-deg), #e2e8f0 var(--score-deg), #e2e8f0 360deg) !important;
}
.score-very-strong {
background: conic-gradient(from 0deg, #047857 0deg, #047857 var(--score-deg), #e2e8f0 var(--score-deg), #e2e8f0 360deg) !important;
}
/* 平板端适配 (768px - 1024px) */
@media (min-width: 768px) and (max-width: 1024px) {
.container {
max-width: 900px;
padding: 25px;
}
.header h1 {
font-size: 2.4rem;
}
.input-container,
.result-container {
padding: 30px;
}
.details-grid {
grid-template-columns: 1fr;
}
.recommendations-section {
grid-template-columns: 1fr;
}
.strength-score {
flex-direction: column;
text-align: center;
gap: 20px;
}
}
/* 手机端适配 (最大767px) */
@media (max-width: 767px) {
.container {
padding: 15px;
max-width: 100%;
}
.header {
padding: 25px 15px;
margin-bottom: 25px;
}
.header h1 {
font-size: 2rem;
}
.subtitle {
font-size: 1rem;
}
.input-container,
.result-container {
padding: 25px;
border-radius: 15px;
}
.main-content {
gap: 20px;
}
.password-input {
padding: 16px 50px 16px 16px;
font-size: 1rem;
}
.check-btn {
padding: 16px 28px;
font-size: 1rem;
}
.strength-overview {
padding: 20px;
margin-bottom: 25px;
}
.strength-score {
flex-direction: column;
text-align: center;
gap: 20px;
}
.score-circle {
width: 100px;
height: 100px;
}
.score-circle::before {
width: 75px;
height: 75px;
}
.score-value {
font-size: 2rem;
}
.strength-level {
font-size: 1.6rem;
}
.details-grid {
grid-template-columns: 1fr;
gap: 20px;
}
.detail-card {
padding: 20px;
}
.character-types {
grid-template-columns: 1fr;
}
.recommendations-section {
grid-template-columns: 1fr;
gap: 20px;
}
.recommendations-card,
.security-tips-card {
padding: 20px;
}
.toast {
right: 15px;
left: 15px;
top: 15px;
text-align: center;
}
}
/* 小屏手机适配 (最大480px) */
@media (max-width: 480px) {
.container {
padding: 10px;
}
.header {
padding: 20px 10px;
margin-bottom: 20px;
}
.header h1 {
font-size: 1.8rem;
}
.input-container,
.result-container {
padding: 20px;
}
.password-input {
padding: 14px 45px 14px 14px;
font-size: 0.95rem;
}
.check-btn {
padding: 14px 24px;
}
.detail-card {
padding: 15px;
}
.card-header h3 {
font-size: 1.1rem;
}
}
/* 触摸设备优化 */
@media (hover: none) and (pointer: coarse) {
.check-btn,
.retry-btn,
.toggle-visibility {
min-height: 44px;
}
.toggle-visibility {
padding: 12px;
}
}
/* 高对比度模式支持 */
@media (prefers-contrast: high) {
.input-container,
.result-container,
.detail-card {
border: 2px solid #2c3e50;
}
.password-input {
border: 2px solid #2c3e50;
}
}
/* 减少动画模式支持 */
@media (prefers-reduced-motion: reduce) {
* {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
/* 深色模式支持 */
@media (prefers-color-scheme: dark) {
body {
background: #0f172a;
color: #e2e8f0;
}
.input-container,
.result-container,
.detail-card,
.recommendations-card,
.security-tips-card {
background: #1e293b;
border-color: #334155;
}
.password-input {
background: #334155;
border-color: #475569;
color: #e2e8f0;
}
.password-input:focus {
background: #1e293b;
border-color: #667eea;
}
.strength-overview {
background: #1e293b;
border-color: #334155;
}
.char-type,
.recommendations-list li,
.tip-item {
background: #334155;
border-color: #475569;
color: #e2e8f0;
}
}
/* 打印样式 */
@media print {
.header {
background: none !important;
color: black !important;
box-shadow: none !important;
}
.check-btn,
.retry-btn,
.toggle-visibility,
.toast {
display: none !important;
}
.input-container,
.result-container {
box-shadow: none !important;
border: 1px solid #ccc !important;
}
}

View File

@@ -0,0 +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>
</html>

View File

@@ -0,0 +1,516 @@
/**
* 密码强度检测器
* 提供密码强度分析和安全建议
*/
class PasswordStrengthChecker {
constructor() {
this.apiUrl = 'https://60s.api.shumengya.top/v2/password/check';
this.isChecking = false;
this.currentPassword = '';
this.init();
}
/**
* 初始化应用
*/
init() {
this.bindEvents();
this.setupFormValidation();
this.hideResultContainer();
this.hideErrorContainer();
console.log('密码强度检测器初始化完成');
}
/**
* 绑定事件监听器
*/
bindEvents() {
// 密码输入框事件
const passwordInput = document.getElementById('passwordInput');
if (passwordInput) {
passwordInput.addEventListener('input', this.handlePasswordInput.bind(this));
passwordInput.addEventListener('keypress', this.handleKeyPress.bind(this));
}
// 显示/隐藏密码按钮
const toggleBtn = document.getElementById('toggleVisibility');
if (toggleBtn) {
toggleBtn.addEventListener('click', this.togglePasswordVisibility.bind(this));
}
// 检测按钮
const checkBtn = document.getElementById('checkBtn');
if (checkBtn) {
checkBtn.addEventListener('click', this.handleCheckPassword.bind(this));
}
// 重试按钮
const retryBtn = document.getElementById('retryBtn');
if (retryBtn) {
retryBtn.addEventListener('click', this.handleRetry.bind(this));
}
}
/**
* 设置表单验证
*/
setupFormValidation() {
const form = document.querySelector('.input-container');
if (form) {
form.addEventListener('submit', (e) => {
e.preventDefault();
this.handleCheckPassword();
});
}
}
/**
* 处理密码输入
*/
handlePasswordInput(event) {
const password = event.target.value;
this.currentPassword = password;
// 更新按钮状态
this.updateCheckButtonState();
// 如果密码为空,隐藏结果
if (!password.trim()) {
this.hideResultContainer();
this.hideErrorContainer();
}
}
/**
* 处理键盘事件
*/
handleKeyPress(event) {
if (event.key === 'Enter' && !this.isChecking) {
event.preventDefault();
this.handleCheckPassword();
}
}
/**
* 切换密码可见性
*/
togglePasswordVisibility() {
const passwordInput = document.getElementById('passwordInput');
const toggleBtn = document.getElementById('toggleVisibility');
if (passwordInput && toggleBtn) {
const isPassword = passwordInput.type === 'password';
passwordInput.type = isPassword ? 'text' : 'password';
toggleBtn.innerHTML = isPassword ? '🙈' : '👁️';
toggleBtn.title = isPassword ? '隐藏密码' : '显示密码';
}
}
/**
* 更新检测按钮状态
*/
updateCheckButtonState() {
const checkBtn = document.getElementById('checkBtn');
const hasPassword = this.currentPassword.trim().length > 0;
if (checkBtn) {
checkBtn.disabled = !hasPassword || this.isChecking;
if (this.isChecking) {
checkBtn.innerHTML = '<span class="btn-icon">⏳</span>检测中...';
} else if (hasPassword) {
checkBtn.innerHTML = '<span class="btn-icon">🔍</span>检测密码强度';
} else {
checkBtn.innerHTML = '<span class="btn-icon">🔍</span>请输入密码';
}
}
}
/**
* 处理密码检测
*/
async handleCheckPassword() {
const password = this.currentPassword.trim();
if (!password) {
this.showToast('请输入要检测的密码', 'error');
return;
}
if (this.isChecking) {
return;
}
try {
this.setLoadingState(true);
this.hideErrorContainer();
const result = await this.checkPasswordStrength(password);
if (result.code === 200 && result.data) {
this.displayResults(result.data);
this.showResultContainer();
this.showToast('密码强度检测完成', 'success');
} else {
throw new Error(result.message || '检测失败');
}
} catch (error) {
console.error('密码检测错误:', error);
this.showError(error.message || '检测服务暂时不可用,请稍后重试');
} finally {
this.setLoadingState(false);
}
}
/**
* 调用API检测密码强度
*/
async checkPasswordStrength(password) {
const url = new URL(this.apiUrl);
url.searchParams.append('password', password);
url.searchParams.append('encoding', 'utf-8');
const response = await fetch(url.toString(), {
method: 'GET',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return await response.json();
}
/**
* 显示检测结果
*/
displayResults(data) {
this.updateStrengthOverview(data);
this.updateDetailedInfo(data);
this.updateRecommendations(data);
}
/**
* 更新强度概览
*/
updateStrengthOverview(data) {
// 更新分数圆圈
const scoreCircle = document.getElementById('scoreCircle');
const scoreValue = document.getElementById('scoreValue');
const strengthLevel = document.getElementById('strengthLevel');
const strengthDescription = document.getElementById('strengthDescription');
const barFill = document.getElementById('strengthBar');
if (scoreValue) {
scoreValue.textContent = data.score || 0;
}
if (strengthLevel) {
strengthLevel.textContent = this.getStrengthText(data.strength);
const strengthClass = this.getStrengthClass(data.strength);
strengthLevel.className = `strength-level strength-${strengthClass}`;
}
if (strengthDescription) {
strengthDescription.textContent = this.getStrengthDescription(data.strength);
}
// 更新分数圆圈
if (scoreCircle) {
const percentage = (data.score / 100) * 360;
scoreCircle.style.setProperty('--score-deg', `${percentage}deg`);
// 将中文强度转换为CSS类名
const strengthClass = this.getStrengthClass(data.strength);
scoreCircle.className = `score-circle score-${strengthClass}`;
}
// 更新强度条
if (barFill) {
setTimeout(() => {
barFill.style.width = `${data.score}%`;
}, 100);
}
}
/**
* 更新详细信息
*/
updateDetailedInfo(data) {
// 基本信息
this.updateElement('passwordLength', data.length || 0);
this.updateElement('entropyValue', data.entropy ? data.entropy.toFixed(2) : '0.00');
this.updateElement('crackTime', data.time_to_crack || '未知');
// 字符类型分析
this.updateCharacterAnalysis(data.character_analysis || {});
}
/**
* 更新字符类型分析
*/
updateCharacterAnalysis(analysis) {
const types = {
'has_lowercase': { element: 'hasLowercase', label: '小写字母', icon: '🔤' },
'has_uppercase': { element: 'hasUppercase', label: '大写字母', icon: '🔠' },
'has_numbers': { element: 'hasNumbers', label: '数字', icon: '🔢' },
'has_symbols': { element: 'hasSymbols', label: '特殊符号', icon: '🔣' }
};
Object.keys(types).forEach(key => {
const element = document.getElementById(types[key].element);
if (element) {
const hasType = analysis[key] || false;
element.className = `char-type ${hasType ? 'has-type' : ''}`;
element.innerHTML = `
<span class="type-icon">${hasType ? '✅' : '❌'}</span>
<span>${types[key].label}</span>
`;
}
});
// 更新字符种类数量
this.updateElement('characterVariety', analysis.character_variety || 0);
// 更新问题提示
this.updateCharacterIssues(analysis);
}
/**
* 更新字符问题提示
*/
updateCharacterIssues(analysis) {
const issues = [
{ id: 'hasRepeated', condition: analysis.has_repeated, text: '包含重复字符' },
{ id: 'hasSequential', condition: analysis.has_sequential, text: '包含连续字符' }
];
issues.forEach(issue => {
const element = document.getElementById(issue.id);
if (element) {
if (issue.condition) {
element.style.display = 'flex';
element.innerHTML = `
<span class="issue-icon">⚠️</span>
<span class="issue-text">${issue.text}</span>
`;
} else {
element.style.display = 'none';
}
}
});
}
/**
* 更新建议和提示
*/
updateRecommendations(data) {
// 更新建议列表
const recommendationsList = document.getElementById('recommendationsList');
if (recommendationsList && data.recommendations) {
recommendationsList.innerHTML = '';
data.recommendations.forEach(recommendation => {
const li = document.createElement('li');
li.textContent = recommendation;
recommendationsList.appendChild(li);
});
}
// 更新安全提示
const tipsContainer = document.getElementById('securityTips');
if (tipsContainer && data.security_tips) {
tipsContainer.innerHTML = '';
data.security_tips.forEach((tip, index) => {
const tipElement = document.createElement('div');
tipElement.className = 'tip-item';
tipElement.innerHTML = `
<span class="tip-icon">${this.getTipIcon(index)}</span>
<span class="tip-text">${tip}</span>
`;
tipsContainer.appendChild(tipElement);
});
}
}
/**
* 获取提示图标
*/
getTipIcon(index) {
const icons = ['🛡️', '🔐', '⚡', '🎯', '💡', '🔄'];
return icons[index % icons.length];
}
/**
* 获取强度文本
*/
getStrengthText(strength) {
// API直接返回中文强度无需映射
return strength || '未知';
}
/**
* 获取强度CSS类名
*/
getStrengthClass(strength) {
const classMap = {
'弱': 'weak',
'中等': 'medium',
'强': 'strong',
'非常强': 'very-strong'
};
return classMap[strength] || 'unknown';
}
/**
* 获取强度描述
*/
getStrengthDescription(strength) {
const descriptions = {
'弱': '密码强度较弱,建议增加复杂度',
'中等': '密码强度中等,可以进一步优化',
'强': '密码强度良好,安全性较高',
'非常强': '密码强度非常好,安全性很高'
};
return descriptions[strength] || '无法评估密码强度';
}
/**
* 设置加载状态
*/
setLoadingState(loading) {
this.isChecking = loading;
this.updateCheckButtonState();
const passwordInput = document.getElementById('passwordInput');
if (passwordInput) {
passwordInput.disabled = loading;
}
}
/**
* 显示结果容器
*/
showResultContainer() {
const container = document.getElementById('resultContainer');
if (container) {
container.style.display = 'block';
container.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
}
/**
* 隐藏结果容器
*/
hideResultContainer() {
const container = document.getElementById('resultContainer');
if (container) {
container.style.display = 'none';
}
}
/**
* 显示错误
*/
showError(message) {
const errorContainer = document.getElementById('errorContainer');
const errorMessage = document.getElementById('errorMessage');
if (errorContainer && errorMessage) {
errorMessage.textContent = message;
errorContainer.style.display = 'block';
this.hideResultContainer();
errorContainer.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
}
/**
* 隐藏错误容器
*/
hideErrorContainer() {
const container = document.getElementById('errorContainer');
if (container) {
container.style.display = 'none';
}
}
/**
* 处理重试
*/
handleRetry() {
this.hideErrorContainer();
const passwordInput = document.getElementById('passwordInput');
if (passwordInput) {
passwordInput.focus();
}
}
/**
* 更新元素内容
*/
updateElement(id, content) {
const element = document.getElementById(id);
if (element) {
element.textContent = content;
}
}
/**
* 显示提示消息
*/
showToast(message, type = 'success') {
const toast = document.getElementById('toast');
const toastMessage = document.getElementById('toastMessage');
if (toast && toastMessage) {
toastMessage.textContent = message;
toast.className = `toast toast-${type}`;
toast.style.display = 'block';
// 3秒后自动隐藏
setTimeout(() => {
toast.style.display = 'none';
}, 3000);
}
}
}
// 页面加载完成后初始化
document.addEventListener('DOMContentLoaded', () => {
try {
window.passwordChecker = new PasswordStrengthChecker();
console.log('密码强度检测器已启动');
} catch (error) {
console.error('初始化失败:', error);
}
});
// 页面可见性变化处理
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'visible' && window.passwordChecker) {
console.log('页面重新激活');
}
});
// 全局错误处理
window.addEventListener('error', (event) => {
console.error('全局错误:', event.error);
if (window.passwordChecker) {
window.passwordChecker.showToast('发生了意外错误,请刷新页面重试', 'error');
}
});
// 网络状态监听
window.addEventListener('online', () => {
if (window.passwordChecker) {
window.passwordChecker.showToast('网络连接已恢复', 'success');
}
});
window.addEventListener('offline', () => {
if (window.passwordChecker) {
window.passwordChecker.showToast('网络连接已断开', 'error');
}
});

View File

@@ -0,0 +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增强安全性",
"避免在公共场合输入密码",
"不要将密码保存在浏览器中(除非使用可信的密码管理器)",
"避免使用个人信息作为密码",
"长密码比复杂密码更安全"
]
}
}

View File

@@ -0,0 +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%);
}
}

View File

@@ -0,0 +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%;
}
}

View File

@@ -0,0 +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>
</html>

View File

@@ -0,0 +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);
});

View File

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

View File

@@ -0,0 +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%;
}
}

View File

@@ -0,0 +1,530 @@
/* 基础样式重置 */
* {
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: #333;
overflow-x: hidden;
}
/* 容器布局 */
.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: 20px 0;
}
.title {
font-size: 2.5rem;
font-weight: 700;
margin-bottom: 10px;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.1);
}
.subtitle {
font-size: 1.1rem;
color: rgba(255, 255, 255, 0.9);
font-weight: 300;
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.2);
}
/* 主内容区域 */
.main {
flex: 1;
display: flex;
flex-direction: column;
gap: 30px;
}
/* 搜索区域 */
.search-section {
display: flex;
justify-content: center;
align-items: center;
}
.search-container {
display: flex;
width: 100%;
max-width: 600px;
background: rgba(255, 255, 255, 0.95);
border-radius: 50px;
padding: 8px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
backdrop-filter: blur(10px);
transition: all 0.3s ease;
}
.search-container:focus-within {
transform: translateY(-2px);
box-shadow: 0 15px 40px rgba(0, 0, 0, 0.3);
}
.search-input {
flex: 1;
border: none;
outline: none;
padding: 15px 25px;
font-size: 1.1rem;
background: transparent;
color: #333;
border-radius: 50px;
}
.search-input::placeholder {
color: #999;
font-weight: 300;
}
.search-btn {
border: none;
outline: none;
padding: 15px 25px;
border-radius: 50px;
color: white;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
display: flex;
align-items: center;
gap: 8px;
transition: all 0.3s ease;
min-width: 120px;
justify-content: center;
}
.search-btn:hover {
transform: scale(1.05);
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
}
.search-btn:active {
transform: scale(0.98);
}
.search-icon {
font-size: 1.2rem;
}
/* 结果区域 */
.result-section {
flex: 1;
display: flex;
justify-content: center;
align-items: flex-start;
min-height: 400px;
}
/* 加载动画 */
.loading {
display: flex;
flex-direction: column;
align-items: center;
gap: 20px;
padding: 40px;
color: rgba(255, 255, 255, 0.9);
}
.loading-spinner {
width: 50px;
height: 50px;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* 结果卡片 */
.result-card {
width: 100%;
max-width: 800px;
background: rgba(255, 255, 255, 0.95);
border-radius: 20px;
padding: 30px;
box-shadow: 0 15px 40px rgba(0, 0, 0, 0.2);
backdrop-filter: blur(10px);
animation: slideUp 0.5s ease;
}
@keyframes slideUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.result-header {
margin-bottom: 25px;
text-align: center;
}
.result-title {
font-size: 2rem;
font-weight: 700;
color: #333;
margin-bottom: 10px;
}
.result-description {
font-size: 1.1rem;
color: #666;
font-weight: 400;
}
.result-content {
display: grid;
grid-template-columns: 1fr 2fr;
gap: 30px;
align-items: start;
}
.result-image-container {
display: flex;
justify-content: center;
}
.result-image {
width: 100%;
max-width: 250px;
height: auto;
border-radius: 15px;
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
transition: transform 0.3s ease;
}
.result-image:hover {
transform: scale(1.05);
}
.result-text {
display: flex;
flex-direction: column;
gap: 20px;
}
.result-abstract h3 {
font-size: 1.3rem;
color: #333;
margin-bottom: 10px;
font-weight: 600;
}
.result-abstract p {
font-size: 1rem;
line-height: 1.8;
color: #555;
text-align: justify;
}
.result-actions {
display: flex;
justify-content: flex-end;
}
.result-link {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 12px 24px;
background: rgba(0, 123, 255, 0.1);
color: #007bff;
text-decoration: none;
border-radius: 25px;
font-weight: 600;
transition: all 0.3s ease;
border: 2px solid transparent;
}
.result-link:hover {
background: rgba(0, 123, 255, 0.2);
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(0, 123, 255, 0.3);
}
.link-icon {
font-size: 1.2rem;
transition: transform 0.3s ease;
}
.result-link:hover .link-icon {
transform: translateX(5px);
}
/* 错误消息 */
.error-message {
display: flex;
flex-direction: column;
align-items: center;
gap: 20px;
padding: 40px;
background: rgba(255, 255, 255, 0.95);
border-radius: 20px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
backdrop-filter: blur(10px);
text-align: center;
max-width: 500px;
width: 100%;
}
.error-icon {
font-size: 3rem;
}
.error-message h3 {
color: #e74c3c;
font-size: 1.5rem;
margin-bottom: 10px;
}
.error-message p {
color: #666;
font-size: 1rem;
line-height: 1.6;
}
.retry-btn {
padding: 12px 24px;
background: #e74c3c;
color: white;
border: none;
border-radius: 25px;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
}
.retry-btn:hover {
background: #c0392b;
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(231, 76, 60, 0.3);
}
/* 欢迎消息 */
.welcome-message {
display: flex;
flex-direction: column;
align-items: center;
gap: 20px;
padding: 60px 40px;
background: rgba(255, 255, 255, 0.9);
border-radius: 20px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
backdrop-filter: blur(10px);
text-align: center;
max-width: 600px;
width: 100%;
}
.welcome-icon {
font-size: 4rem;
opacity: 0.8;
}
.welcome-message h3 {
color: #333;
font-size: 1.8rem;
font-weight: 600;
margin-bottom: 10px;
}
.welcome-message p {
color: #666;
font-size: 1.1rem;
line-height: 1.6;
max-width: 400px;
}
/* 页脚 */
.footer {
text-align: center;
padding: 20px 0;
margin-top: 40px;
color: rgba(255, 255, 255, 0.8);
font-size: 0.9rem;
}
/* 平板端适配 (768px - 1024px) */
@media (max-width: 1024px) and (min-width: 768px) {
.container {
padding: 15px;
}
.title {
font-size: 2.2rem;
}
.result-content {
grid-template-columns: 1fr 1.5fr;
gap: 25px;
}
.result-card {
padding: 25px;
}
}
/* 手机端适配 (最大768px) */
@media (max-width: 768px) {
.container {
padding: 10px;
}
.header {
margin-bottom: 30px;
padding: 15px 0;
}
.title {
font-size: 1.8rem;
}
.subtitle {
font-size: 1rem;
}
.search-container {
max-width: 100%;
padding: 6px;
}
.search-input {
padding: 12px 20px;
font-size: 1rem;
}
.search-btn {
padding: 12px 20px;
min-width: 100px;
font-size: 0.9rem;
}
.search-text {
display: none;
}
.result-card {
padding: 20px;
border-radius: 15px;
}
.result-title {
font-size: 1.5rem;
}
.result-description {
font-size: 1rem;
}
.result-content {
grid-template-columns: 1fr;
gap: 20px;
}
.result-image {
max-width: 200px;
}
.result-abstract h3 {
font-size: 1.2rem;
}
.result-abstract p {
font-size: 0.95rem;
line-height: 1.7;
}
.result-actions {
justify-content: center;
}
.welcome-message {
padding: 40px 20px;
}
.welcome-icon {
font-size: 3rem;
}
.welcome-message h3 {
font-size: 1.5rem;
}
.welcome-message p {
font-size: 1rem;
}
.error-message {
padding: 30px 20px;
}
.error-icon {
font-size: 2.5rem;
}
.error-message h3 {
font-size: 1.3rem;
}
}
/* 小屏手机适配 (最大480px) */
@media (max-width: 480px) {
.title {
font-size: 1.6rem;
}
.search-container {
flex-direction: column;
gap: 10px;
padding: 15px;
border-radius: 20px;
}
.search-input {
border-radius: 15px;
text-align: center;
}
.search-btn {
border-radius: 15px;
justify-content: center;
}
.search-text {
display: inline;
}
.result-card {
padding: 15px;
}
.result-title {
font-size: 1.3rem;
}
.result-image {
max-width: 150px;
}
}

View File

@@ -0,0 +1,83 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<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

@@ -0,0 +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('百度百科词条查询应用已初始化');
});

View File

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

View File

@@ -0,0 +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"
}
}

View File

@@ -0,0 +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);
}
}

View File

@@ -0,0 +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>
</html>

View File

@@ -0,0 +1,515 @@
// 身体健康分析 JavaScript 功能
// DOM 元素获取
const healthForm = document.getElementById('healthForm');
const analyzeBtn = document.getElementById('analyzeBtn');
const btnText = analyzeBtn.querySelector('.btn-text');
const loadingSpinner = analyzeBtn.querySelector('.loading-spinner');
const resultSection = document.getElementById('resultSection');
const errorSection = document.getElementById('errorSection');
const resetBtn = document.getElementById('resetBtn');
const retryBtn = document.getElementById('retryBtn');
// API 配置
const API_BASE_URL = 'https://60s.api.shumengya.top/v2/health';
// 表单验证规则
const validationRules = {
height: {
min: 100,
max: 250,
message: '身高应在100-250cm之间'
},
weight: {
min: 30,
max: 200,
message: '体重应在30-200kg之间'
},
age: {
min: 1,
max: 120,
message: '年龄应在1-120岁之间'
}
};
// 初始化
document.addEventListener('DOMContentLoaded', function() {
initializeEventListeners();
setupFormValidation();
});
// 事件监听器初始化
function initializeEventListeners() {
healthForm.addEventListener('submit', handleFormSubmit);
resetBtn.addEventListener('click', resetForm);
retryBtn.addEventListener('click', retryAnalysis);
// 输入框实时验证
const inputs = healthForm.querySelectorAll('input, select');
inputs.forEach(input => {
input.addEventListener('blur', validateField);
input.addEventListener('input', clearFieldError);
});
}
// 表单验证设置
function setupFormValidation() {
const inputs = healthForm.querySelectorAll('input[type="number"]');
inputs.forEach(input => {
input.addEventListener('input', function() {
// 移除非数字字符
this.value = this.value.replace(/[^0-9.]/g, '');
// 防止多个小数点
const parts = this.value.split('.');
if (parts.length > 2) {
this.value = parts[0] + '.' + parts.slice(1).join('');
}
});
});
}
// 表单提交处理
async function handleFormSubmit(event) {
event.preventDefault();
if (!validateForm()) {
return;
}
const formData = getFormData();
try {
setLoadingState(true);
hideAllSections();
const result = await callHealthAPI(formData);
displayResults(result);
} catch (error) {
console.error('分析失败:', error);
displayError(error.message || '分析失败,请稍后重试');
} finally {
setLoadingState(false);
}
}
// 获取表单数据
function getFormData() {
return {
height: parseInt(document.getElementById('height').value),
weight: parseInt(document.getElementById('weight').value),
age: parseInt(document.getElementById('age').value),
gender: document.getElementById('gender').value
};
}
// 表单验证
function validateForm() {
let isValid = true;
const inputs = healthForm.querySelectorAll('input, select');
inputs.forEach(input => {
if (!validateField({ target: input })) {
isValid = false;
}
});
return isValid;
}
// 单个字段验证
function validateField(event) {
const field = event.target;
const value = field.value.trim();
const fieldName = field.name;
// 清除之前的错误状态
clearFieldError(event);
// 必填验证
if (!value) {
showFieldError(field, '此字段为必填项');
return false;
}
// 数值范围验证
if (validationRules[fieldName]) {
const numValue = parseFloat(value);
const rule = validationRules[fieldName];
if (numValue < rule.min || numValue > rule.max) {
showFieldError(field, rule.message);
return false;
}
}
return true;
}
// 显示字段错误
function showFieldError(field, message) {
field.classList.add('error');
// 移除已存在的错误消息
const existingError = field.parentNode.querySelector('.error-message');
if (existingError) {
existingError.remove();
}
// 添加错误消息
const errorDiv = document.createElement('div');
errorDiv.className = 'error-message';
errorDiv.textContent = message;
errorDiv.style.color = '#dc3545';
errorDiv.style.fontSize = '0.875rem';
errorDiv.style.marginTop = '5px';
field.parentNode.appendChild(errorDiv);
}
// 清除字段错误
function clearFieldError(event) {
const field = event.target;
field.classList.remove('error');
const errorMessage = field.parentNode.querySelector('.error-message');
if (errorMessage) {
errorMessage.remove();
}
}
// 调用健康分析API
async function callHealthAPI(data) {
const params = new URLSearchParams({
height: data.height,
weight: data.weight,
age: data.age,
gender: data.gender
});
const response = await fetch(`${API_BASE_URL}?${params}`);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const result = await response.json();
if (result.code !== 200) {
throw new Error(result.message || '分析失败');
}
return result.data;
}
// 显示分析结果
function displayResults(data) {
// 基本信息
displayBasicInfo(data.basic_info);
// BMI 分析
displayBMIInfo(data.bmi);
// 体重评估
displayWeightAssessment(data.weight_assessment);
// 代谢分析
displayMetabolism(data.metabolism);
// 体脂分析
displayBodyFat(data.body_fat);
// 理想三围
displayMeasurements(data.ideal_measurements);
// 健康建议
displayHealthAdvice(data.health_advice);
// 免责声明
displayDisclaimer(data.disclaimer);
// 显示结果区域
resultSection.style.display = 'block';
resultSection.scrollIntoView({ behavior: 'smooth' });
}
// 显示基本信息
function displayBasicInfo(basicInfo) {
const container = document.getElementById('basicInfo');
container.innerHTML = '';
const infoItems = [
{ label: basicInfo.height_desc, value: basicInfo.height },
{ label: basicInfo.weight_desc, value: basicInfo.weight },
{ label: basicInfo.age_desc, value: basicInfo.age },
{ label: basicInfo.gender_desc, value: basicInfo.gender }
];
infoItems.forEach(item => {
const itemDiv = createInfoItem(item.label, item.value);
container.appendChild(itemDiv);
});
}
// 显示BMI信息
function displayBMIInfo(bmiData) {
const container = document.getElementById('bmiContent');
container.innerHTML = `
<div class="bmi-value">${bmiData.value}</div>
<div class="bmi-category">${bmiData.category}</div>
<div class="info-grid">
${createInfoItem(bmiData.evaluation_desc, bmiData.evaluation).outerHTML}
${createInfoItem(bmiData.risk_desc, bmiData.risk).outerHTML}
</div>
`;
}
// 显示体重评估
function displayWeightAssessment(weightData) {
const container = document.getElementById('weightContent');
container.innerHTML = '';
const items = [
{ label: weightData.ideal_weight_range_desc, value: weightData.ideal_weight_range },
{ label: weightData.standard_weight_desc, value: weightData.standard_weight },
{ label: weightData.status_desc, value: weightData.status },
{ label: weightData.adjustment_desc, value: weightData.adjustment }
];
const grid = document.createElement('div');
grid.className = 'info-grid';
items.forEach(item => {
const itemDiv = createInfoItem(item.label, item.value);
grid.appendChild(itemDiv);
});
container.appendChild(grid);
}
// 显示代谢分析
function displayMetabolism(metabolismData) {
const container = document.getElementById('metabolismContent');
container.innerHTML = '';
const items = [
{ label: metabolismData.bmr_desc, value: metabolismData.bmr },
{ label: metabolismData.tdee_desc, value: metabolismData.tdee },
{ label: metabolismData.recommended_calories_desc, value: metabolismData.recommended_calories },
{ label: metabolismData.weight_loss_calories_desc, value: metabolismData.weight_loss_calories },
{ label: metabolismData.weight_gain_calories_desc, value: metabolismData.weight_gain_calories }
];
const grid = document.createElement('div');
grid.className = 'info-grid';
items.forEach(item => {
const itemDiv = createInfoItem(item.label, item.value);
grid.appendChild(itemDiv);
});
container.appendChild(grid);
}
// 显示体脂分析
function displayBodyFat(bodyFatData) {
const container = document.getElementById('bodyFatContent');
container.innerHTML = '';
const items = [
{ label: bodyFatData.percentage_desc, value: bodyFatData.percentage },
{ label: bodyFatData.category_desc, value: bodyFatData.category },
{ label: bodyFatData.fat_weight_desc, value: bodyFatData.fat_weight },
{ label: bodyFatData.lean_weight_desc, value: bodyFatData.lean_weight }
];
const grid = document.createElement('div');
grid.className = 'info-grid';
items.forEach(item => {
const itemDiv = createInfoItem(item.label, item.value);
grid.appendChild(itemDiv);
});
container.appendChild(grid);
}
// 显示理想三围
function displayMeasurements(measurementsData) {
const container = document.getElementById('measurementsContent');
container.innerHTML = '';
const items = [
{ label: measurementsData.chest_desc, value: measurementsData.chest },
{ label: measurementsData.waist_desc, value: measurementsData.waist },
{ label: measurementsData.hip_desc, value: measurementsData.hip }
];
const grid = document.createElement('div');
grid.className = 'info-grid';
items.forEach(item => {
const itemDiv = createInfoItem(item.label, item.value);
grid.appendChild(itemDiv);
});
// 添加说明
const note = document.createElement('p');
note.style.marginTop = '15px';
note.style.fontSize = '0.9rem';
note.style.color = '#4a7c59';
note.style.textAlign = 'center';
note.textContent = measurementsData.note;
container.appendChild(grid);
container.appendChild(note);
}
// 显示健康建议
function displayHealthAdvice(adviceData) {
const container = document.getElementById('adviceContent');
container.innerHTML = '';
// 饮水量建议
const waterDiv = createAdviceSection(adviceData.daily_water_intake_desc, adviceData.daily_water_intake);
container.appendChild(waterDiv);
// 运动建议
const exerciseDiv = createAdviceSection(adviceData.exercise_recommendation_desc, adviceData.exercise_recommendation);
container.appendChild(exerciseDiv);
// 营养建议
const nutritionDiv = createAdviceSection(adviceData.nutrition_advice_desc, adviceData.nutrition_advice);
container.appendChild(nutritionDiv);
// 健康提示
const tipsDiv = document.createElement('div');
tipsDiv.innerHTML = `
<h4 style="color: #2d5a3d; margin-bottom: 10px;">${adviceData.health_tips_desc}</h4>
<ul class="health-tips"></ul>
`;
const tipsList = tipsDiv.querySelector('.health-tips');
adviceData.health_tips.forEach(tip => {
const li = document.createElement('li');
li.textContent = tip;
tipsList.appendChild(li);
});
container.appendChild(tipsDiv);
}
// 创建建议区块
function createAdviceSection(title, content) {
const div = document.createElement('div');
div.style.marginBottom = '20px';
div.innerHTML = `
<h4 style="color: #2d5a3d; margin-bottom: 8px;">${title}</h4>
<p style="background: #f8fff8; padding: 12px; border-radius: 8px; border-left: 4px solid #4caf50; line-height: 1.6;">${content}</p>
`;
return div;
}
// 显示免责声明
function displayDisclaimer(disclaimer) {
const container = document.getElementById('disclaimer');
container.textContent = disclaimer;
}
// 创建信息项
function createInfoItem(label, value) {
const div = document.createElement('div');
div.className = 'info-item';
div.innerHTML = `
<div class="info-label">${label}</div>
<div class="info-value">${value}</div>
`;
return div;
}
// 显示错误信息
function displayError(message) {
const errorMessage = document.getElementById('errorMessage');
errorMessage.textContent = message;
errorSection.style.display = 'block';
errorSection.scrollIntoView({ behavior: 'smooth' });
}
// 设置加载状态
function setLoadingState(isLoading) {
if (isLoading) {
analyzeBtn.disabled = true;
btnText.style.display = 'none';
loadingSpinner.style.display = 'block';
} else {
analyzeBtn.disabled = false;
btnText.style.display = 'block';
loadingSpinner.style.display = 'none';
}
}
// 隐藏所有结果区域
function hideAllSections() {
resultSection.style.display = 'none';
errorSection.style.display = 'none';
}
// 重置表单
function resetForm() {
healthForm.reset();
hideAllSections();
// 清除所有错误状态
const errorInputs = healthForm.querySelectorAll('.error');
errorInputs.forEach(input => {
input.classList.remove('error');
});
const errorMessages = healthForm.querySelectorAll('.error-message');
errorMessages.forEach(msg => msg.remove());
// 滚动到表单顶部
healthForm.scrollIntoView({ behavior: 'smooth' });
}
// 重试分析
function retryAnalysis() {
hideAllSections();
healthForm.scrollIntoView({ behavior: 'smooth' });
}
// 工具函数:防抖
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// 添加CSS样式到错误输入框
const style = document.createElement('style');
style.textContent = `
.form-input.error,
.form-select.error {
border-color: #dc3545 !important;
box-shadow: 0 0 0 3px rgba(220, 53, 69, 0.1) !important;
}
`;
document.head.appendChild(style);
// 页面可见性变化处理(用户切换标签页时暂停动画等)
document.addEventListener('visibilitychange', function() {
if (document.hidden) {
// 页面隐藏时的处理
document.body.style.animationPlayState = 'paused';
} else {
// 页面显示时的处理
document.body.style.animationPlayState = 'running';
}
});

View File

@@ -0,0 +1,697 @@
/* 基础样式重置 */
* {
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;
padding: 20px 0;
}
.title {
font-size: 2.5rem;
font-weight: 700;
color: #1a4d2e;
margin-bottom: 10px;
text-shadow: 0 2px 4px rgba(26, 77, 46, 0.1);
}
.subtitle {
font-size: 1.1rem;
color: #4a7c59;
font-weight: 400;
}
/* 主内容区域 */
.main-content {
flex: 1;
display: flex;
flex-direction: column;
gap: 30px;
}
/* 表单区域 */
.form-section {
background: rgba(255, 255, 255, 0.95);
border-radius: 20px;
padding: 30px;
box-shadow: 0 8px 32px rgba(26, 77, 46, 0.1);
border: 1px solid rgba(144, 238, 144, 0.3);
}
.health-form {
display: grid;
gap: 20px;
}
.form-group {
display: flex;
flex-direction: column;
gap: 8px;
}
.form-label {
font-weight: 600;
color: #2d5a3d;
font-size: 1rem;
}
.form-input,
.form-select {
padding: 15px 20px;
border: 2px solid #a8e6a3;
border-radius: 12px;
font-size: 1rem;
background: #f8fff8;
color: #2d5a3d;
transition: all 0.3s ease;
}
.form-input:focus,
.form-select:focus {
outline: none;
border-color: #4caf50;
box-shadow: 0 0 0 3px rgba(76, 175, 80, 0.1);
background: #ffffff;
}
.form-input::placeholder {
color: #81c784;
}
/* 提交按钮 */
.submit-btn {
background: linear-gradient(135deg, #4caf50, #66bb6a);
color: white;
border: none;
padding: 18px 30px;
border-radius: 12px;
font-size: 1.1rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
margin-top: 10px;
}
.submit-btn:hover {
background: linear-gradient(135deg, #45a049, #5cb85c);
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(76, 175, 80, 0.3);
}
.submit-btn:active {
transform: translateY(0);
}
.submit-btn:disabled {
background: #c8e6c9;
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;
margin: 0 auto;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* 结果区域 */
.result-section {
animation: fadeInUp 0.6s ease-out;
}
.result-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 25px;
flex-wrap: wrap;
gap: 15px;
}
.result-title {
font-size: 2rem;
color: #1a4d2e;
font-weight: 700;
}
.reset-btn {
background: #e8f5e8;
color: #2d5a3d;
border: 2px solid #a8e6a3;
padding: 10px 20px;
border-radius: 8px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
}
.reset-btn:hover {
background: #d4edda;
border-color: #4caf50;
}
/* 结果卡片 */
.result-content {
display: grid;
gap: 20px;
}
.basic-info-card,
.bmi-card,
.weight-card,
.metabolism-card,
.body-fat-card,
.measurements-card,
.advice-card,
.disclaimer-card {
background: rgba(255, 255, 255, 0.95);
border-radius: 16px;
padding: 25px;
box-shadow: 0 6px 20px rgba(26, 77, 46, 0.08);
border: 1px solid rgba(144, 238, 144, 0.2);
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.basic-info-card:hover,
.bmi-card:hover,
.weight-card:hover,
.metabolism-card:hover,
.body-fat-card:hover,
.measurements-card:hover,
.advice-card:hover {
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(26, 77, 46, 0.12);
}
.card-title {
font-size: 1.4rem;
color: #1a4d2e;
font-weight: 700;
margin-bottom: 15px;
border-bottom: 2px solid #e8f5e8;
padding-bottom: 10px;
}
/* 信息网格 */
.info-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
}
.info-item {
background: #f8fff8;
padding: 15px;
border-radius: 10px;
border-left: 4px solid #4caf50;
}
.info-label {
font-size: 0.9rem;
color: #4a7c59;
font-weight: 600;
margin-bottom: 5px;
}
.info-value {
font-size: 1.2rem;
color: #2d5a3d;
font-weight: 700;
}
/* BMI 特殊样式 */
.bmi-value {
font-size: 2.5rem;
font-weight: 800;
color: #4caf50;
text-align: center;
margin: 15px 0;
}
.bmi-category {
text-align: center;
font-size: 1.3rem;
font-weight: 600;
color: #2d5a3d;
background: #e8f5e8;
padding: 10px;
border-radius: 8px;
margin: 10px 0;
}
/* 健康建议列表 */
.health-tips {
list-style: none;
padding: 0;
}
.health-tips li {
background: #f8fff8;
margin: 10px 0;
padding: 12px 15px;
border-radius: 8px;
border-left: 4px solid #81c784;
position: relative;
}
.health-tips li::before {
content: "✓";
color: #4caf50;
font-weight: bold;
margin-right: 10px;
}
/* 免责声明 */
.disclaimer {
background: #fff3cd;
border: 1px solid #ffeaa7;
color: #856404;
padding: 15px;
border-radius: 8px;
font-size: 0.95rem;
line-height: 1.5;
text-align: center;
}
/* 错误区域 */
.error-section {
text-align: center;
padding: 40px 20px;
}
.error-content {
background: rgba(255, 255, 255, 0.95);
border-radius: 16px;
padding: 30px;
box-shadow: 0 6px 20px rgba(220, 53, 69, 0.1);
border: 1px solid rgba(220, 53, 69, 0.2);
max-width: 400px;
margin: 0 auto;
}
.error-title {
color: #dc3545;
font-size: 1.5rem;
margin-bottom: 15px;
}
.error-message {
color: #6c757d;
margin-bottom: 20px;
}
.retry-btn {
background: #dc3545;
color: white;
border: none;
padding: 12px 24px;
border-radius: 8px;
font-weight: 600;
cursor: pointer;
transition: background 0.3s ease;
}
.retry-btn:hover {
background: #c82333;
}
/* 底部 */
.footer {
text-align: center;
padding: 20px 0;
margin-top: 30px;
border-top: 1px solid rgba(144, 238, 144, 0.3);
}
.footer-text {
color: #4a7c59;
font-size: 0.9rem;
}
/* 动画效果 */
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* 平板端适配 (768px - 1024px) */
@media (min-width: 768px) and (max-width: 1024px) {
.container {
padding: 30px;
}
.title {
font-size: 2.8rem;
}
.form-section {
padding: 35px;
}
.health-form {
grid-template-columns: repeat(2, 1fr);
gap: 25px;
}
.form-group:last-child {
grid-column: 1 / -1;
}
.result-content {
grid-template-columns: repeat(2, 1fr);
}
.advice-card,
.disclaimer-card {
grid-column: 1 / -1;
}
}
/* 电脑端适配 (1024px+) */
@media (min-width: 1024px) {
.container {
padding: 40px;
max-width: 1400px;
}
.title {
font-size: 3.2rem;
}
.main-content {
flex-direction: row;
gap: 40px;
align-items: flex-start;
}
.form-section {
flex: 0 0 380px;
position: sticky;
top: 20px;
max-height: calc(100vh - 40px);
overflow-y: auto;
}
.result-section,
.error-section {
flex: 1;
min-width: 0;
}
/* 桌面端结果区域重新设计 - 使用更清晰的布局 */
.result-content {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 25px;
grid-auto-rows: min-content;
}
/* 基本信息卡片 - 占满第一行 */
.basic-info-card {
grid-column: 1 / -1;
}
/* 第二行BMI、体重评估、代谢分析 */
.bmi-card,
.weight-card,
.metabolism-card {
grid-column: span 1;
}
/* 第三行:体脂分析和理想三围 */
.body-fat-card {
grid-column: span 2;
}
.measurements-card {
grid-column: span 1;
}
/* 第四行:健康建议 - 占满整行 */
.advice-card {
grid-column: 1 / -1;
}
/* 第五行:免责声明 - 占满整行 */
.disclaimer-card {
grid-column: 1 / -1;
}
/* 基本信息网格优化 */
.basic-info-card .info-grid {
grid-template-columns: repeat(4, 1fr);
gap: 20px;
}
/* BMI卡片特殊布局 */
.bmi-card {
display: flex;
flex-direction: column;
min-height: 280px;
}
.bmi-card .bmi-content {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
}
.bmi-value {
font-size: 3rem;
margin: 20px 0;
}
/* 体重评估卡片布局优化 */
.weight-card {
display: flex;
flex-direction: column;
min-height: 280px;
}
.weight-card .weight-content {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.weight-card .info-grid {
grid-template-columns: 1fr;
gap: 12px;
}
/* 代谢分析卡片布局优化 */
.metabolism-card {
display: flex;
flex-direction: column;
min-height: 280px;
}
.metabolism-card .metabolism-content {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.metabolism-card .info-grid {
grid-template-columns: 1fr;
gap: 12px;
}
/* 体脂分析卡片网格优化 */
.body-fat-card .info-grid {
grid-template-columns: repeat(2, 1fr);
gap: 15px;
}
/* 理想三围卡片网格优化 */
.measurements-card {
display: flex;
flex-direction: column;
min-height: 200px;
}
.measurements-card .measurements-content {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
}
.measurements-card .info-grid {
grid-template-columns: 1fr;
gap: 15px;
}
/* 健康建议卡片布局优化 */
.advice-card {
padding: 30px;
}
.advice-card .advice-content {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 25px;
margin-bottom: 25px;
}
.advice-card .health-tips {
grid-column: 1 / -1;
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 15px;
list-style: none;
padding: 0;
}
/* 表单区域优化 */
.health-form {
display: grid;
gap: 25px;
}
.form-group {
margin-bottom: 0;
}
.submit-btn {
margin-top: 20px;
padding: 20px 30px;
font-size: 1.2rem;
}
}
/* 手机端适配 (最高优先级) */
@media (max-width: 767px) {
.container {
padding: 15px;
}
.header {
margin-bottom: 20px;
padding: 15px 0;
}
.title {
font-size: 2rem;
}
.subtitle {
font-size: 1rem;
}
.form-section {
padding: 20px;
border-radius: 16px;
}
.form-input,
.form-select {
padding: 12px 16px;
font-size: 16px; /* 防止iOS缩放 */
}
.submit-btn {
padding: 16px 24px;
font-size: 1rem;
}
.result-header {
flex-direction: column;
align-items: stretch;
gap: 15px;
}
.result-title {
font-size: 1.6rem;
text-align: center;
}
.reset-btn {
align-self: center;
padding: 12px 24px;
}
.basic-info-card,
.bmi-card,
.weight-card,
.metabolism-card,
.body-fat-card,
.measurements-card,
.advice-card,
.disclaimer-card {
padding: 20px;
border-radius: 12px;
}
.card-title {
font-size: 1.2rem;
}
.info-grid {
grid-template-columns: 1fr;
gap: 12px;
}
.bmi-value {
font-size: 2rem;
}
.bmi-category {
font-size: 1.1rem;
}
.health-tips li {
padding: 10px 12px;
font-size: 0.95rem;
}
.error-content {
padding: 25px 20px;
}
}

View File

@@ -0,0 +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": "结果基于通用公式和统计数据,仅供参考,不能替代专业医疗建议。如有健康问题,请咨询医生。"
}
}

View File

@@ -0,0 +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;
}
}

View File

@@ -0,0 +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>
</html>

View File

@@ -0,0 +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);
}

View File

@@ -0,0 +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;
}
}

View File

@@ -0,0 +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",
"品牌设计",
"室内设计",
"服装搭配"
]
}
}
}

View File

@@ -0,0 +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);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +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>
</html>

View File

@@ -0,0 +1,617 @@
// 链接OG信息查询 - JavaScript功能代码
// 神秘高级风格的交互体验
class OGAnalyzer {
constructor() {
this.apiUrl = 'https://60s.api.shumengya.top/v2/og';
this.isAnalyzing = false;
this.currentUrl = '';
this.animationFrameId = null;
this.init();
}
init() {
this.bindEvents();
this.createBackgroundEffects();
this.initializeAnimations();
this.showWelcomeMessage();
this.initPageAnimations();
}
// 初始化页面动画
initPageAnimations() {
// 延迟添加动画类确保CSS已加载
setTimeout(() => {
const header = document.querySelector('.header');
const querySection = document.querySelector('.query-section');
if (header) header.classList.add('animate-in');
if (querySection) querySection.classList.add('animate-in');
}, 100);
}
bindEvents() {
const urlInput = document.getElementById('url-input');
const analyzeBtn = document.getElementById('analyze-btn');
const copyBtn = document.getElementById('copy-btn');
const clearBtn = document.getElementById('clear-btn');
// 输入框事件
urlInput.addEventListener('input', (e) => this.handleUrlInput(e));
urlInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter' && !this.isAnalyzing) {
this.analyzeUrl();
}
});
urlInput.addEventListener('focus', () => this.handleInputFocus());
urlInput.addEventListener('blur', () => this.handleInputBlur());
// 按钮事件
analyzeBtn.addEventListener('click', () => this.analyzeUrl());
copyBtn.addEventListener('click', () => this.copyResults());
clearBtn.addEventListener('click', () => this.clearResults());
// 键盘快捷键
document.addEventListener('keydown', (e) => this.handleKeyboard(e));
}
handleUrlInput(e) {
const url = e.target.value.trim();
const analyzeBtn = document.getElementById('analyze-btn');
if (this.isValidUrl(url)) {
analyzeBtn.classList.add('ready');
e.target.classList.remove('error');
} else {
analyzeBtn.classList.remove('ready');
if (url.length > 0) {
e.target.classList.add('error');
} else {
e.target.classList.remove('error');
}
}
}
handleInputFocus() {
const inputContainer = document.querySelector('.input-container');
inputContainer.classList.add('focused');
this.createInputGlow();
}
handleInputBlur() {
const inputContainer = document.querySelector('.input-container');
inputContainer.classList.remove('focused');
}
handleKeyboard(e) {
// Ctrl/Cmd + Enter 快速分析
if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
e.preventDefault();
if (!this.isAnalyzing) {
this.analyzeUrl();
}
}
// Escape 清除结果
if (e.key === 'Escape') {
this.clearResults();
}
}
isValidUrl(string) {
try {
const url = new URL(string);
return url.protocol === 'http:' || url.protocol === 'https:';
} catch (_) {
return false;
}
}
async analyzeUrl() {
const urlInput = document.getElementById('url-input');
const url = urlInput.value.trim();
if (!this.isValidUrl(url)) {
this.showError('请输入有效的URL地址');
this.shakeInput();
return;
}
if (this.isAnalyzing) {
return;
}
this.currentUrl = url;
this.isAnalyzing = true;
this.startTime = Date.now(); // 记录开始时间
this.showLoading();
this.hideError();
this.hideResults();
try {
const response = await fetch(`${this.apiUrl}?url=${encodeURIComponent(url)}`);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
if (data.code === 200 && data.data) {
await this.displayResults(data.data);
this.showSuccessMessage('分析完成!');
// 添加按钮闪烁效果
const analyzeBtn = document.getElementById('analyze-btn');
analyzeBtn.classList.add('flash');
setTimeout(() => {
analyzeBtn.classList.remove('flash');
}, 300);
} else {
throw new Error(data.message || '获取OG信息失败');
}
} catch (error) {
console.error('分析失败:', error);
this.showError(`分析失败: ${error.message}`);
} finally {
this.isAnalyzing = false;
this.hideLoading();
}
}
showLoading() {
const loadingElement = document.getElementById('loading');
const analyzeBtn = document.getElementById('analyze-btn');
loadingElement.classList.add('active');
analyzeBtn.disabled = true;
analyzeBtn.textContent = '分析中...';
this.startScannerAnimation();
}
hideLoading() {
const loadingElement = document.getElementById('loading');
const analyzeBtn = document.getElementById('analyze-btn');
loadingElement.classList.remove('active');
analyzeBtn.disabled = false;
analyzeBtn.textContent = '开始分析';
this.stopScannerAnimation();
}
async displayResults(data) {
const resultsElement = document.getElementById('results');
const ogCard = document.getElementById('og-card');
// 检查是否有有效数据 - 放宽检查条件,只要有任何非空字段就显示
const hasValidData = Object.values(data).some(value => {
if (value === null || value === undefined) return false;
if (typeof value === 'string') return value.trim() !== '';
return true; // 其他类型的值都认为是有效的
});
if (!hasValidData) {
this.showError('该链接暂无可获取的OG信息请检查链接是否正确或稍后重试');
return;
}
// 基础信息 - 只显示有数据的字段
this.updateElementWithVisibility('og-title', data.title, '标题');
this.updateElementWithVisibility('og-description', data.description, '描述');
this.updateElement('og-url', data.url || this.currentUrl); // URL始终显示
this.updateElementWithVisibility('og-site-name', data.site_name, '网站名称');
this.updateElement('og-type', data.type || 'website'); // 类型始终显示
// 媒体信息
this.updateImageElementWithVisibility('og-image', data.image);
this.updateElementWithVisibility('og-image-alt', data.image_alt, '图片描述');
// 技术信息
this.updateElementWithVisibility('og-locale', data.locale, '语言');
this.updateElementWithVisibility('og-updated-time', this.formatDate(data.updated_time), '更新时间');
this.updateElement('response-time', `${Date.now() - this.startTime}ms`); // 响应时间始终显示
// 显示结果
resultsElement.style.display = 'block';
resultsElement.classList.add('active');
// 添加动画效果
await this.animateResults();
// 启用操作按钮
document.getElementById('copy-btn').disabled = false;
document.getElementById('clear-btn').disabled = false;
}
updateElement(id, content) {
const element = document.getElementById(id);
if (element) {
element.textContent = content;
}
}
updateElementWithVisibility(id, content, fieldName) {
const element = document.getElementById(id);
if (!element) return;
const parentItem = element.closest('.info-item');
if (!parentItem) return;
if (content && content.trim() !== '') {
element.textContent = content;
parentItem.style.display = 'block';
} else {
parentItem.style.display = 'none';
}
}
updateImageElement(id, imageSrc) {
const element = document.getElementById(id);
if (element && imageSrc) {
element.src = imageSrc;
element.style.display = 'block';
element.onerror = () => {
element.style.display = 'none';
const placeholder = element.nextElementSibling;
if (placeholder && placeholder.classList.contains('image-placeholder')) {
placeholder.style.display = 'flex';
}
};
} else if (element) {
element.style.display = 'none';
const placeholder = element.nextElementSibling;
if (placeholder && placeholder.classList.contains('image-placeholder')) {
placeholder.style.display = 'flex';
}
}
}
updateImageElementWithVisibility(id, imageSrc) {
const element = document.getElementById(id);
const mediaSection = document.querySelector('.media-info');
const mediaPreview = document.getElementById('media-preview');
if (imageSrc && imageSrc.trim() !== '') {
element.textContent = imageSrc;
if (mediaSection) mediaSection.style.display = 'block';
if (mediaPreview) {
mediaPreview.innerHTML = `
<img src="${imageSrc}" alt="OG Image" class="og-preview-image"
onerror="this.style.display='none'; this.nextElementSibling.style.display='block';">
<div class="no-media" style="display: none;">
<i class="fas fa-image-slash"></i>
<span>图片加载失败</span>
</div>
`;
}
} else {
if (mediaSection) mediaSection.style.display = 'none';
}
}
formatDate(timestamp) {
if (!timestamp) return '未知';
try {
const date = new Date(timestamp);
return date.toLocaleString('zh-CN');
} catch (e) {
return '格式错误';
}
}
async animateResults() {
const cards = document.querySelectorAll('.info-card');
for (let i = 0; i < cards.length; i++) {
setTimeout(() => {
cards[i].classList.add('animate-in');
}, i * 100);
}
// 等待动画完成
await new Promise(resolve => setTimeout(resolve, cards.length * 100 + 300));
}
showError(message) {
const errorElement = document.getElementById('error-message');
const errorText = errorElement.querySelector('.error-text');
const inputContainer = document.querySelector('.input-container');
errorText.textContent = message;
errorElement.classList.add('active');
// 添加震动效果
if (inputContainer) {
inputContainer.classList.add('shake');
setTimeout(() => {
inputContainer.classList.remove('shake');
}, 600);
}
// 自动隐藏错误信息
setTimeout(() => {
this.hideError();
}, 5000);
}
hideError() {
const errorElement = document.getElementById('error-message');
errorElement.classList.remove('active');
}
hideResults() {
const resultsElement = document.getElementById('results');
resultsElement.style.display = 'none';
resultsElement.classList.remove('active');
// 重置动画状态
const cards = document.querySelectorAll('.info-card');
cards.forEach(card => card.classList.remove('animate-in'));
}
showSuccessMessage(message) {
const tipElement = document.getElementById('tip-message');
const tipText = tipElement.querySelector('.tip-text');
tipText.textContent = message;
tipElement.classList.add('active');
setTimeout(() => {
tipElement.classList.remove('active');
}, 3000);
}
shakeInput() {
const inputContainer = document.querySelector('.input-container');
inputContainer.classList.add('shake');
setTimeout(() => {
inputContainer.classList.remove('shake');
}, 600);
}
copyResults() {
const ogData = {
title: document.getElementById('og-title').textContent,
description: document.getElementById('og-description').textContent,
url: document.getElementById('og-url').textContent,
site_name: document.getElementById('og-site-name').textContent,
type: document.getElementById('og-type').textContent,
image: document.getElementById('og-image').src,
locale: document.getElementById('og-locale').textContent
};
const jsonString = JSON.stringify(ogData, null, 2);
navigator.clipboard.writeText(jsonString).then(() => {
this.showSuccessMessage('结果已复制到剪贴板!');
this.flashCopyButton();
}).catch(err => {
console.error('复制失败:', err);
this.showError('复制失败,请手动选择内容');
});
}
flashCopyButton() {
const copyBtn = document.getElementById('copy-btn');
copyBtn.classList.add('flash');
setTimeout(() => {
copyBtn.classList.remove('flash');
}, 300);
}
clearResults() {
const urlInput = document.getElementById('url-input');
const resultsElement = document.getElementById('results');
const errorElement = document.getElementById('error-message');
urlInput.value = '';
urlInput.classList.remove('error');
resultsElement.style.display = 'none';
resultsElement.classList.remove('active');
errorElement.classList.remove('active');
document.getElementById('analyze-btn').classList.remove('ready');
document.getElementById('copy-btn').disabled = true;
document.getElementById('clear-btn').disabled = true;
this.currentUrl = '';
// 重置所有字段的显示状态
const infoItems = document.querySelectorAll('.info-item');
infoItems.forEach(item => item.style.display = 'block');
const mediaSection = document.querySelector('.media-info');
if (mediaSection) mediaSection.style.display = 'block';
// 重置动画状态
const cards = document.querySelectorAll('.info-card');
cards.forEach(card => card.classList.remove('animate-in'));
this.showSuccessMessage('已清除所有内容');
}
createBackgroundEffects() {
const container = document.querySelector('.background-container');
// 创建各种背景效果层
const effects = [
'geometric-grid',
'neural-network',
'particle-system',
'scan-lines',
'holographic-overlay',
'data-stream',
'quantum-waves'
];
effects.forEach(effectClass => {
const layer = document.createElement('div');
layer.className = effectClass;
container.appendChild(layer);
});
}
createInputGlow() {
const inputContainer = document.querySelector('.input-container');
// 创建光晕效果
const glow = document.createElement('div');
glow.className = 'input-glow';
inputContainer.appendChild(glow);
setTimeout(() => {
if (glow.parentNode) {
glow.remove();
}
}, 2000);
}
startScannerAnimation() {
const scanner = document.querySelector('.scanner');
if (scanner) {
scanner.classList.add('active');
}
}
stopScannerAnimation() {
const scanner = document.querySelector('.scanner');
if (scanner) {
scanner.classList.remove('active');
}
}
initializeAnimations() {
// 初始化页面动画
const header = document.querySelector('.header');
const querySection = document.querySelector('.query-section');
setTimeout(() => {
header.classList.add('animate-in');
}, 100);
setTimeout(() => {
querySection.classList.add('animate-in');
}, 300);
}
showWelcomeMessage() {
const tips = [
'支持分析网页的标题、描述、图片等元信息',
'可以预览社交媒体分享时的显示效果',
'检测网页的SEO优化情况',
'分析Open Graph协议标签'
];
setTimeout(() => {
this.showSuccessMessage('欢迎使用链接OG信息分析器');
}, 1000);
// 显示提示信息
this.showTips(tips);
}
// 显示提示信息
showTips(tips) {
const tipElement = document.getElementById('tip-message');
const tipText = tipElement.querySelector('.tip-text');
let currentTip = 0;
const showNextTip = () => {
tipText.textContent = tips[currentTip];
tipElement.classList.add('active');
tipElement.style.animation = 'fadeInUp 0.5s ease-out';
setTimeout(() => {
tipElement.style.animation = 'fadeOutDown 0.5s ease-in';
setTimeout(() => {
tipElement.classList.remove('active');
currentTip = (currentTip + 1) % tips.length;
}, 500);
}, 3000);
};
// 首次显示
setTimeout(showNextTip, 2000);
// 每8秒显示一次
setInterval(showNextTip, 8000);
}
}
// 工具函数
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('DOMContentLoaded', () => {
// 检查必要的DOM元素
const requiredElements = [
'url-input', 'analyze-btn', 'copy-btn', 'clear-btn',
'loading', 'results', 'error-message', 'tip-message'
];
const missingElements = requiredElements.filter(id => !document.getElementById(id));
if (missingElements.length > 0) {
console.error('缺少必要的DOM元素:', missingElements);
return;
}
// 初始化应用
window.ogAnalyzer = new OGAnalyzer();
// 添加全局错误处理
window.addEventListener('error', (e) => {
console.error('全局错误:', e.error);
if (window.ogAnalyzer) {
window.ogAnalyzer.showError('发生未知错误,请刷新页面重试');
}
});
// 添加网络状态监听
window.addEventListener('online', () => {
if (window.ogAnalyzer) {
window.ogAnalyzer.showSuccessMessage('网络连接已恢复');
}
});
window.addEventListener('offline', () => {
if (window.ogAnalyzer) {
window.ogAnalyzer.showError('网络连接已断开');
}
});
});
// 导出给其他模块使用
if (typeof module !== 'undefined' && module.exports) {
module.exports = { OGAnalyzer, debounce, throttle };
}

View File

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

View File

@@ -0,0 +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
}

View File

@@ -0,0 +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;
}

View File

@@ -0,0 +1,647 @@
/* 基础样式重置 */
* {
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;
overflow-x: hidden;
}
/* 容器布局 */
.container {
max-width: 800px;
margin: 0 auto;
padding: 20px;
min-height: 100vh;
display: flex;
flex-direction: column;
}
/* 头部样式 */
.header {
text-align: center;
margin-bottom: 40px;
padding: 30px 20px;
background: linear-gradient(135deg, #e8f5e8 0%, #f0f9f0 100%);
border-radius: 20px;
box-shadow: 0 4px 20px rgba(45, 90, 61, 0.1);
}
.header h1 {
font-size: 2.5rem;
font-weight: 700;
color: #2d5a3d;
margin-bottom: 10px;
text-shadow: 0 2px 4px rgba(45, 90, 61, 0.1);
}
.subtitle {
font-size: 1.1rem;
color: #5a8a6b;
font-weight: 400;
}
/* 主内容区域 */
.main-content {
flex: 1;
display: flex;
flex-direction: column;
gap: 30px;
}
/* 表单容器 */
.form-container {
background: #ffffff;
border-radius: 16px;
padding: 30px;
box-shadow: 0 8px 32px rgba(45, 90, 61, 0.1);
border: 1px solid #e8f5e8;
}
.password-form {
display: flex;
flex-direction: column;
gap: 25px;
}
/* 表单组样式 */
.form-group {
display: flex;
flex-direction: column;
gap: 12px;
}
.form-group label {
font-weight: 600;
color: #2d5a3d;
font-size: 1rem;
}
.section-title {
font-size: 1.1rem;
color: #2d5a3d;
font-weight: 600;
margin-bottom: 8px;
}
/* 长度控制 */
.length-control {
display: flex;
align-items: center;
gap: 15px;
padding: 15px;
background: #f8fdf8;
border-radius: 12px;
border: 2px solid #e8f5e8;
}
.length-slider {
flex: 1;
height: 6px;
background: #e8f5e8;
border-radius: 3px;
outline: none;
-webkit-appearance: none;
}
.length-slider::-webkit-slider-thumb {
-webkit-appearance: none;
width: 20px;
height: 20px;
background: linear-gradient(135deg, #4caf50, #45a049);
border-radius: 50%;
cursor: pointer;
box-shadow: 0 2px 8px rgba(76, 175, 80, 0.3);
}
.length-slider::-moz-range-thumb {
width: 20px;
height: 20px;
background: linear-gradient(135deg, #4caf50, #45a049);
border-radius: 50%;
cursor: pointer;
border: none;
box-shadow: 0 2px 8px rgba(76, 175, 80, 0.3);
}
.length-display {
min-width: 40px;
text-align: center;
font-weight: 700;
font-size: 1.2rem;
color: #2d5a3d;
background: #ffffff;
padding: 8px 12px;
border-radius: 8px;
border: 2px solid #e8f5e8;
}
/* 复选框组 */
.checkbox-group {
display: flex;
flex-direction: column;
gap: 12px;
}
.checkbox-item {
display: flex;
align-items: center;
gap: 12px;
padding: 12px 16px;
background: #f8fdf8;
border-radius: 10px;
border: 2px solid #e8f5e8;
transition: all 0.3s ease;
cursor: pointer;
}
.checkbox-item:hover {
background: #f0f9f0;
border-color: #d4edda;
transform: translateY(-1px);
}
.checkbox-item input[type="checkbox"] {
width: 18px;
height: 18px;
accent-color: #4caf50;
cursor: pointer;
}
.checkbox-item label {
flex: 1;
cursor: pointer;
font-weight: 500;
color: #2d5a3d;
margin: 0;
}
/* 生成按钮 */
.generate-btn {
background: linear-gradient(135deg, #4caf50, #45a049);
color: white;
border: none;
padding: 16px 32px;
border-radius: 12px;
font-size: 1.1rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 4px 16px rgba(76, 175, 80, 0.3);
position: relative;
overflow: hidden;
}
.generate-btn:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(76, 175, 80, 0.4);
}
.generate-btn:active {
transform: translateY(0);
}
.generate-btn:disabled {
opacity: 0.7;
cursor: not-allowed;
transform: none;
}
/* 结果容器 */
.result-container {
background: #ffffff;
border-radius: 16px;
padding: 30px;
box-shadow: 0 8px 32px rgba(45, 90, 61, 0.1);
border: 1px solid #e8f5e8;
animation: slideIn 0.5s ease-out;
}
@keyframes slideIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.result-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.result-header h3 {
color: #2d5a3d;
font-size: 1.3rem;
font-weight: 600;
}
.copy-btn {
background: #4caf50;
color: white;
border: none;
padding: 10px;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
}
.copy-btn:hover {
background: #45a049;
transform: scale(1.05);
}
/* 密码显示 */
.password-display {
margin-bottom: 25px;
}
.password-input {
width: 100%;
padding: 16px 20px;
border: 2px solid #e8f5e8;
border-radius: 12px;
font-family: 'Courier New', monospace;
font-size: 1.1rem;
font-weight: 600;
color: #2d5a3d;
background: #f8fdf8;
text-align: center;
letter-spacing: 1px;
word-break: break-all;
}
.password-input:focus {
outline: none;
border-color: #4caf50;
box-shadow: 0 0 0 3px rgba(76, 175, 80, 0.1);
}
/* 密码信息 */
.password-info {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 15px;
margin-bottom: 25px;
}
.info-item {
display: flex;
flex-direction: column;
gap: 5px;
padding: 12px 16px;
background: #f8fdf8;
border-radius: 10px;
border: 1px solid #e8f5e8;
}
.info-item.full-width {
grid-column: 1 / -1;
}
.info-label {
font-size: 0.9rem;
color: #5a8a6b;
font-weight: 500;
}
.info-value {
font-size: 1rem;
color: #2d5a3d;
font-weight: 600;
}
.info-value.strength {
padding: 4px 8px;
border-radius: 6px;
text-align: center;
color: white;
font-weight: 700;
}
.strength.weak {
background: #f44336;
}
.strength.medium {
background: #ff9800;
}
.strength.strong {
background: #4caf50;
}
.strength.very-strong {
background: #2e7d32;
}
/* 字符集显示 */
.character-sets {
border-top: 1px solid #e8f5e8;
padding-top: 20px;
}
.character-sets h4 {
color: #2d5a3d;
margin-bottom: 15px;
font-size: 1.1rem;
}
.sets-list {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.set-item {
background: #e8f5e8;
color: #2d5a3d;
padding: 6px 12px;
border-radius: 20px;
font-size: 0.9rem;
font-weight: 500;
}
/* 错误容器 */
.error-container {
background: #ffffff;
border-radius: 16px;
padding: 40px 30px;
text-align: center;
box-shadow: 0 8px 32px rgba(244, 67, 54, 0.1);
border: 1px solid #ffebee;
}
.error-icon {
font-size: 3rem;
margin-bottom: 15px;
}
.error-container h3 {
color: #d32f2f;
margin-bottom: 10px;
font-size: 1.3rem;
}
.error-container p {
color: #666;
margin-bottom: 20px;
}
.retry-btn {
background: #f44336;
color: white;
border: none;
padding: 12px 24px;
border-radius: 8px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
}
.retry-btn:hover {
background: #d32f2f;
transform: translateY(-1px);
}
/* 页脚 */
.footer {
text-align: center;
padding: 30px 20px;
color: #5a8a6b;
font-size: 0.9rem;
}
/* 提示框 */
.toast {
position: fixed;
top: 20px;
right: 20px;
background: #4caf50;
color: white;
padding: 12px 20px;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(76, 175, 80, 0.3);
z-index: 1000;
animation: toastSlide 0.3s ease-out;
}
@keyframes toastSlide {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
/* 平板端适配 (768px - 1024px) */
@media (min-width: 768px) and (max-width: 1024px) {
.container {
max-width: 700px;
padding: 25px;
}
.header h1 {
font-size: 2.2rem;
}
.form-container,
.result-container {
padding: 25px;
}
.password-info {
grid-template-columns: repeat(2, 1fr);
}
}
/* 手机端适配 (最大767px) */
@media (max-width: 767px) {
.container {
padding: 15px;
max-width: 100%;
}
.header {
padding: 20px 15px;
margin-bottom: 25px;
}
.header h1 {
font-size: 1.8rem;
}
.subtitle {
font-size: 1rem;
}
.form-container,
.result-container {
padding: 20px;
border-radius: 12px;
}
.password-form {
gap: 20px;
}
.form-group {
gap: 10px;
}
.length-control {
padding: 12px;
gap: 12px;
}
.length-display {
min-width: 35px;
padding: 6px 10px;
font-size: 1.1rem;
}
.checkbox-item {
padding: 10px 12px;
gap: 10px;
}
.checkbox-item input[type="checkbox"] {
width: 16px;
height: 16px;
}
.generate-btn {
padding: 14px 28px;
font-size: 1rem;
}
.password-input {
padding: 14px 16px;
font-size: 1rem;
letter-spacing: 0.5px;
}
.password-info {
grid-template-columns: 1fr;
gap: 12px;
}
.info-item {
padding: 10px 12px;
}
.result-header {
flex-direction: column;
gap: 15px;
align-items: stretch;
}
.copy-btn {
align-self: center;
padding: 12px 20px;
border-radius: 10px;
}
.toast {
right: 15px;
left: 15px;
top: 15px;
text-align: center;
}
}
/* 小屏手机适配 (最大480px) */
@media (max-width: 480px) {
.container {
padding: 10px;
}
.header {
padding: 15px 10px;
margin-bottom: 20px;
}
.header h1 {
font-size: 1.6rem;
}
.form-container,
.result-container {
padding: 15px;
}
.checkbox-item {
padding: 8px 10px;
}
.generate-btn {
padding: 12px 24px;
}
.password-input {
padding: 12px 14px;
font-size: 0.95rem;
}
}
/* 触摸设备优化 */
@media (hover: none) and (pointer: coarse) {
.checkbox-item,
.generate-btn,
.copy-btn,
.retry-btn {
min-height: 44px;
}
.checkbox-item input[type="checkbox"] {
width: 20px;
height: 20px;
}
.length-slider::-webkit-slider-thumb {
width: 24px;
height: 24px;
}
}
/* 高对比度模式支持 */
@media (prefers-contrast: high) {
.form-container,
.result-container {
border: 2px solid #2d5a3d;
}
.checkbox-item {
border: 1px solid #2d5a3d;
}
.password-input {
border: 2px solid #2d5a3d;
}
}
/* 减少动画模式支持 */
@media (prefers-reduced-motion: reduce) {
* {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}

View File

@@ -0,0 +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>
</html>

View File

@@ -0,0 +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();
});

View File

@@ -0,0 +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": "数百万年"
}
}
}

View File

@@ -0,0 +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;
}
}

View File

@@ -0,0 +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>
</html>

View File

@@ -0,0 +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);
});

View File

@@ -0,0 +1,637 @@
/* 基础样式重置 */
* {
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;
}
/* 头部样式 */
.header {
text-align: center;
margin-bottom: 30px;
padding: 20px 0;
}
.header h1 {
font-size: 2rem;
color: #2d5a27;
margin-bottom: 10px;
font-weight: 600;
}
.subtitle {
color: #4a5568;
font-size: 1rem;
opacity: 0.8;
}
/* 主要内容区域 */
.main-content {
display: flex;
flex-direction: column;
gap: 30px;
}
/* 输入区域 */
.input-section {
background: rgba(255, 255, 255, 0.9);
padding: 25px;
border-radius: 15px;
box-shadow: 0 4px 20px rgba(45, 90, 39, 0.1);
border: 1px solid rgba(144, 205, 144, 0.3);
}
.input-group {
margin-bottom: 20px;
}
.input-group label {
display: block;
margin-bottom: 8px;
font-weight: 500;
color: #2d5a27;
font-size: 0.95rem;
}
.input-group input,
.input-group select {
width: 100%;
padding: 12px 15px;
border: 2px solid #90cd90;
border-radius: 10px;
font-size: 1rem;
transition: all 0.3s ease;
background: #f8fff8;
}
.input-group input:focus,
.input-group select:focus {
outline: none;
border-color: #2d5a27;
box-shadow: 0 0 0 3px rgba(45, 90, 39, 0.1);
background: #ffffff;
}
.button-group {
display: flex;
gap: 15px;
margin-top: 25px;
}
.btn {
flex: 1;
padding: 15px 20px;
border: none;
border-radius: 10px;
font-size: 1rem;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
text-transform: none;
}
.btn-primary {
background: linear-gradient(135deg, #2d5a27, #4a7c59);
color: white;
}
.btn-primary:hover {
background: linear-gradient(135deg, #1e3a1a, #2d5a27);
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(45, 90, 39, 0.3);
}
.btn-secondary {
background: linear-gradient(135deg, #90cd90, #a8d8a8);
color: #2d5a27;
}
.btn-secondary:hover {
background: linear-gradient(135deg, #7bb87b, #90cd90);
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(144, 205, 144, 0.4);
}
/* 结果展示区域 */
.result-section {
background: rgba(255, 255, 255, 0.9);
padding: 25px;
border-radius: 15px;
box-shadow: 0 4px 20px rgba(45, 90, 39, 0.1);
border: 1px solid rgba(144, 205, 144, 0.3);
}
/* 颜色预览 */
.color-preview {
display: flex;
align-items: center;
gap: 20px;
margin-bottom: 30px;
padding: 20px;
background: #f8fff8;
border-radius: 12px;
border: 1px solid rgba(144, 205, 144, 0.2);
}
.color-box {
width: 80px;
height: 80px;
border-radius: 12px;
border: 3px solid #ffffff;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
flex-shrink: 0;
}
.color-info h3 {
color: #2d5a27;
margin-bottom: 5px;
font-size: 1.2rem;
}
.color-info p {
color: #4a5568;
font-size: 1.1rem;
font-weight: 500;
font-family: 'Courier New', monospace;
}
/* 颜色格式展示 */
.color-formats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.format-group {
background: #f8fff8;
padding: 15px;
border-radius: 10px;
border: 1px solid rgba(144, 205, 144, 0.2);
}
.format-group h4 {
color: #2d5a27;
margin-bottom: 10px;
font-size: 1rem;
font-weight: 600;
}
.format-values {
display: flex;
gap: 8px;
margin-bottom: 8px;
flex-wrap: wrap;
}
.format-values span {
background: #90cd90;
color: #2d5a27;
padding: 4px 8px;
border-radius: 6px;
font-size: 0.85rem;
font-weight: 500;
}
.format-group p {
font-family: 'Courier New', monospace;
color: #4a5568;
font-size: 0.9rem;
background: #ffffff;
padding: 8px;
border-radius: 6px;
border: 1px solid rgba(144, 205, 144, 0.2);
}
/* 颜色属性 */
.color-properties {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
margin-bottom: 30px;
}
.property-item {
display: flex;
justify-content: space-between;
align-items: center;
background: #f8fff8;
padding: 12px 15px;
border-radius: 8px;
border: 1px solid rgba(144, 205, 144, 0.2);
}
.property-item label {
color: #2d5a27;
font-weight: 500;
font-size: 0.9rem;
}
.property-item span {
color: #4a5568;
font-weight: 600;
font-family: 'Courier New', monospace;
}
/* 配色方案 */
.color-palette {
margin-bottom: 30px;
}
.color-palette h4 {
color: #2d5a27;
margin-bottom: 15px;
font-size: 1.1rem;
font-weight: 600;
}
.palette-group {
display: flex;
flex-direction: column;
gap: 15px;
}
.palette-item {
background: #f8fff8;
padding: 15px;
border-radius: 10px;
border: 1px solid rgba(144, 205, 144, 0.2);
}
.palette-item label {
display: block;
color: #2d5a27;
font-weight: 500;
margin-bottom: 10px;
font-size: 0.95rem;
}
.color-sample {
width: 40px;
height: 40px;
border-radius: 8px;
border: 2px solid #ffffff;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
display: inline-block;
margin-right: 10px;
}
.analogous-colors,
.triadic-colors {
display: flex;
gap: 10px;
margin-bottom: 8px;
}
.analogous-hex,
.triadic-hex {
display: flex;
gap: 10px;
font-family: 'Courier New', monospace;
font-size: 0.85rem;
color: #4a5568;
}
/* 无障碍性信息 */
.accessibility-info h4 {
color: #2d5a27;
margin-bottom: 15px;
font-size: 1.1rem;
font-weight: 600;
}
.accessibility-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 10px;
}
.accessibility-item {
display: flex;
justify-content: space-between;
align-items: center;
background: #f8fff8;
padding: 10px 15px;
border-radius: 8px;
border: 1px solid rgba(144, 205, 144, 0.2);
}
.accessibility-item span:first-child {
color: #2d5a27;
font-weight: 500;
font-size: 0.9rem;
}
.status {
padding: 4px 8px;
border-radius: 6px;
font-size: 0.8rem;
font-weight: 600;
}
.status.pass {
background: #90cd90;
color: #2d5a27;
}
.status.fail {
background: #ffcccb;
color: #d32f2f;
}
/* 加载和错误状态 */
.loading,
.error {
text-align: center;
padding: 40px 20px;
border-radius: 12px;
margin: 20px 0;
}
.loading {
background: rgba(144, 205, 144, 0.1);
border: 1px solid rgba(144, 205, 144, 0.3);
}
.error {
background: rgba(255, 204, 203, 0.3);
border: 1px solid rgba(211, 47, 47, 0.3);
}
.spinner {
width: 40px;
height: 40px;
border: 4px solid rgba(144, 205, 144, 0.3);
border-top: 4px solid #2d5a27;
border-radius: 50%;
animation: spin 1s linear infinite;
margin: 0 auto 15px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.loading p {
color: #2d5a27;
font-weight: 500;
}
.error p {
color: #d32f2f;
font-weight: 500;
}
/* 平板端适配 (768px - 1024px) */
@media (min-width: 768px) and (max-width: 1024px) {
.container {
padding: 30px;
}
.header h1 {
font-size: 2.5rem;
}
.main-content {
gap: 35px;
}
.input-section,
.result-section {
padding: 30px;
}
.color-preview {
gap: 25px;
}
.color-box {
width: 100px;
height: 100px;
}
.color-formats {
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
}
.palette-group {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
}
}
/* 电脑端适配 (1025px+) */
@media (min-width: 1025px) {
.container {
padding: 40px;
}
.header h1 {
font-size: 3rem;
}
.subtitle {
font-size: 1.1rem;
}
.main-content {
gap: 40px;
}
.input-section,
.result-section {
padding: 35px;
}
.color-preview {
gap: 30px;
padding: 25px;
}
.color-box {
width: 120px;
height: 120px;
}
.color-info h3 {
font-size: 1.4rem;
}
.color-info p {
font-size: 1.2rem;
}
.color-formats {
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 25px;
}
.format-group {
padding: 20px;
}
.palette-group {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 25px;
}
.button-group {
max-width: 500px;
margin: 25px auto 0;
}
.btn {
padding: 18px 25px;
font-size: 1.1rem;
}
}
/* 手机端优化 (最高优先级) */
@media (max-width: 767px) {
.container {
padding: 15px;
}
.header {
margin-bottom: 25px;
padding: 15px 0;
}
.header h1 {
font-size: 1.8rem;
}
.subtitle {
font-size: 0.9rem;
}
.main-content {
gap: 25px;
}
.input-section,
.result-section {
padding: 20px;
border-radius: 12px;
}
.input-group {
margin-bottom: 18px;
}
.input-group input,
.input-group select {
padding: 14px 12px;
font-size: 16px; /* 防止iOS缩放 */
}
.button-group {
flex-direction: column;
gap: 12px;
margin-top: 20px;
}
.btn {
padding: 16px 20px;
font-size: 1rem;
border-radius: 8px;
}
.color-preview {
flex-direction: column;
text-align: center;
gap: 15px;
padding: 15px;
}
.color-box {
width: 100px;
height: 100px;
margin: 0 auto;
}
.color-formats {
grid-template-columns: 1fr;
gap: 15px;
}
.format-group {
padding: 12px;
}
.format-values {
justify-content: center;
}
.color-properties {
grid-template-columns: 1fr;
gap: 12px;
}
.property-item {
flex-direction: column;
gap: 5px;
text-align: center;
padding: 15px;
}
.palette-group {
gap: 12px;
}
.palette-item {
padding: 12px;
text-align: center;
}
.analogous-colors,
.triadic-colors {
justify-content: center;
}
.analogous-hex,
.triadic-hex {
justify-content: center;
flex-wrap: wrap;
}
.accessibility-grid {
grid-template-columns: 1fr;
gap: 8px;
}
.accessibility-item {
flex-direction: column;
gap: 5px;
text-align: center;
padding: 12px;
}
.loading,
.error {
padding: 30px 15px;
margin: 15px 0;
}
.spinner {
width: 35px;
height: 35px;
}
}

View File

@@ -0,0 +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"
]
}
}