This commit is contained in:
yangyaoxiang666
2025-09-06 09:54:54 +08:00
27 changed files with 1358 additions and 232 deletions

View File

@@ -6,6 +6,11 @@
InfoGenie 是一个前后端分离的多功能聚合应用提供实时数据接口、休闲游戏、AI工具等丰富功能。
### 🌐 部署环境
- **前端部署地址**: https://infogenie.shumengya.top
- **后端部署地址**: https://infogenie.api.shumengya.top
### 🏗️ 技术架构
- **前端**: React + Styled Components + React Router
@@ -48,6 +53,60 @@ cd backend
pip install -r requirements.txt
```
## 🚢 部署指南
### 🖥️ 前端部署
1. 进入前端目录:`cd frontend/react-app`
2. 安装依赖:`npm install`
3. 构建生产环境应用:`npm run build`
4.`build` 目录下的所有文件上传到前端服务器的网站根目录
也可以直接运行 `frontend/react-app/deploy.bat` 脚本进行构建。
### ⚙️ 后端部署
1. 进入后端目录:`cd backend`
2. 安装依赖:`pip install -r requirements.txt`
3. 配置环境变量或创建 `.env` 文件,包含以下内容:
```
MONGO_URI=你的MongoDB连接字符串
MAIL_USERNAME=你的邮箱地址
MAIL_PASSWORD=你的邮箱授权码
SECRET_KEY=你的应用密钥
SESSION_COOKIE_SECURE=True
```
4. 使用 Gunicorn 或 uWSGI 作为 WSGI 服务器启动应用:
```
gunicorn -w 4 -b 0.0.0.0:5000 "app:create_app()"
```
5. 配置反向代理,将 `https://infogenie.api.shumengya.top` 反向代理到后端服务
也可以参考 `backend/deploy.bat` 脚本中的部署说明。
### ⚙️ 配置说明
#### 前端配置
前端通过环境变量配置API基础URL
- 开发环境:`.env.development` 文件中设置 `REACT_APP_API_URL=http://localhost:5000`
- 生产环境:`.env.production` 文件中设置 `REACT_APP_API_URL=https://infogenie.api.shumengya.top`
#### 后端配置
后端通过 `config.py` 和环境变量进行配置:
- MongoDB连接通过环境变量 `MONGO_URI` 设置
- 邮件服务:通过环境变量 `MAIL_USERNAME` 和 `MAIL_PASSWORD` 设置
- CORS配置在 `app.py` 中配置允许的前端域名
#### 60sAPI配置
60sAPI模块的静态文件位于 `frontend/60sapi` 目录,通过后端的静态文件服务提供访问。
各API模块的接口地址已配置为 `https://infogenie.api.shumengya.top/api/60s`。
#### 前端依赖
```bash
cd frontend/react-app

14
backend/.env.production Normal file
View File

@@ -0,0 +1,14 @@
# 生产环境配置
# MongoDB配置
MONGO_URI=mongodb://用户名:密码@主机地址:端口/InfoGenie?authSource=admin
# 邮件配置
MAIL_USERNAME=your-email@qq.com
MAIL_PASSWORD=your-app-password
# 应用密钥
SECRET_KEY=infogenie-production-secret-key-2025
# 会话安全配置
SESSION_COOKIE_SECURE=True

View File

@@ -31,7 +31,7 @@ def create_app():
# 加载配置
app.config.from_object(Config)
# 启用CORS跨域支持
# 启用CORS跨域支持(允许所有源)
CORS(app, supports_credentials=True)
# 初始化MongoDB
@@ -182,6 +182,4 @@ def create_app():
if __name__ == '__main__':
app = create_app()
print("🚀 启动 InfoGenie 后端服务...")
print("📡 API地址: http://localhost:5000")
print("📚 文档地址: http://localhost:5000/api/health")
app.run(debug=True, host='0.0.0.0', port=5000)
app.run(debug=True, host='0.0.0.0', port=5002)

27
backend/deploy.bat Normal file
View File

@@ -0,0 +1,27 @@
@echo off
echo ===== 开始部署后端应用到生产环境 =====
cd /d "%~dp0"
echo 1. 安装依赖...
pip install -r requirements.txt
echo 2. 部署说明:
echo.
echo 请确保以下配置已完成:
echo 1. 在服务器上配置环境变量或创建 .env 文件,包含以下内容:
echo MONGO_URI=你的MongoDB连接字符串
echo MAIL_USERNAME=你的邮箱地址
echo MAIL_PASSWORD=你的邮箱授权码
echo SECRET_KEY=你的应用密钥
echo.
echo 3. 启动后端服务:
echo 在生产环境中,建议使用 Gunicorn 或 uWSGI 作为 WSGI 服务器
echo 示例命令gunicorn -w 4 -b 0.0.0.0:5000 "app:create_app()"
echo.
echo 4. 配置反向代理:
echo 将 https://infogenie.api.shumengya.top 反向代理到后端服务
echo.
echo ===== 后端应用部署准备完成 =====
pause

View File

@@ -73,10 +73,13 @@ def scan_directories():
except:
title = module_name
# 根据环境获取基础URL
base_url = 'https://infogenie.api.shumengya.top'
apis.append({
'title': title,
'description': f'{module_name}相关功能',
'link': f'http://localhost:5000/60sapi/{category_name}/{module_name}/index.html',
'link': f'{base_url}/60sapi/{category_name}/{module_name}/index.html',
'status': 'active',
'color': gradient_colors[i % len(gradient_colors)]
})

6
build_frontend.bat Normal file
View File

@@ -0,0 +1,6 @@
@echo off
cd /d "e:\Python\InfoGenie\frontend\react-app"
npm run build
npx serve -s build
pause

View File

@@ -159,13 +159,24 @@ class IPQueryApp {
queryTimeElement.textContent = now.toLocaleString('zh-CN');
}
// 更新详细信息
this.updateDetailItem('location', data.location || '未知');
this.updateDetailItem('isp', data.isp || '未知');
this.updateDetailItem('country', data.country || '未知');
this.updateDetailItem('region', data.region || '未知');
this.updateDetailItem('city', data.city || '未知');
this.updateDetailItem('timezone', data.timezone || '未知');
// 更新详细信息 - 只显示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';
@@ -180,6 +191,23 @@ class IPQueryApp {
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';
}
}
}

View File

@@ -1,3 +1,3 @@
[
"https://60s.viki.moe/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

@@ -1,20 +1,21 @@
/* 玻璃拟态背景相关样式 */
/* 农历主题背景样式 - 动态调节版本 */
body {
background: linear-gradient(135deg,
#667eea 0%,
#764ba2 25%,
#f093fb 50%,
#f5576c 75%,
#4facfe 100%
#fff8dc 0%, /* 玉米丝色 */
#ffd700 20%, /* 金黄色 */
#ffcc00 40%, /* 亮金色 */
#daa520 60%, /* 深金色 */
#b8860b 80%, /* 暗金色 */
#fff8dc 100% /* 玉米丝色 */
);
background-size: 400% 400%;
animation: gradientShift 20s ease infinite;
animation: goldenShift 25s ease infinite;
background-attachment: fixed;
min-height: 100vh;
position: relative;
}
@keyframes gradientShift {
@keyframes goldenShift {
0% { background-position: 0% 50%; }
25% { background-position: 100% 50%; }
50% { background-position: 100% 100%; }
@@ -22,7 +23,56 @@ body {
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;
@@ -30,17 +80,70 @@ body::before {
left: 0;
width: 100%;
height: 100%;
background:
radial-gradient(circle at 20% 20%, rgba(255, 255, 255, 0.1) 0%, transparent 40%),
radial-gradient(circle at 80% 80%, rgba(255, 255, 255, 0.08) 0%, transparent 40%),
radial-gradient(circle at 40% 60%, rgba(255, 255, 255, 0.06) 0%, transparent 30%),
radial-gradient(circle at 60% 30%, rgba(255, 255, 255, 0.05) 0%, transparent 35%);
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: -1;
animation: glassFloat 25s ease-in-out infinite alternate;
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;
@@ -49,41 +152,424 @@ body::after {
width: 100%;
height: 100%;
background-image:
radial-gradient(circle at 10% 20%, rgba(255, 255, 255, 0.3) 2px, transparent 2px),
radial-gradient(circle at 30% 40%, rgba(255, 255, 255, 0.25) 3px, transparent 3px),
radial-gradient(circle at 50% 60%, rgba(255, 255, 255, 0.2) 1.5px, transparent 1.5px),
radial-gradient(circle at 70% 80%, rgba(255, 255, 255, 0.3) 2.5px, transparent 2.5px),
radial-gradient(circle at 90% 10%, rgba(255, 255, 255, 0.25) 2px, transparent 2px),
radial-gradient(circle at 20% 90%, rgba(255, 255, 255, 0.2) 1px, transparent 1px);
background-size: 300px 300px, 250px 250px, 400px 400px, 200px 200px, 350px 350px, 150px 150px;
animation: bubbleFloat 30s linear infinite;
/* 主稻穗茎秆 - 右侧大型 */
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;
opacity: 0.7;
animation: wheatSway 25s ease-in-out infinite;
}
@keyframes glassFloat {
@keyframes spiralRotate {
0% {
transform: translateY(0px) rotate(0deg);
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(-20px) rotate(2deg);
opacity: 0.9;
transform: translateY(100vh) translateX(50px) rotate(360deg);
opacity: 0;
}
}
@keyframes bubbleFloat {
0%, 100% {
transform: translateX(0) translateY(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;
}
25% {
transform: translateX(-15px) translateY(-10px);
}
50% {
transform: translateX(10px) translateY(-20px);
}
75% {
transform: translateX(-5px) translateY(-15px);
.meteor,
.particle {
display: none;
}
}

View File

@@ -10,6 +10,30 @@ body {
line-height: 1.6;
color: #2c3e50;
overflow-x: hidden;
animation: textColorShift 25s ease infinite;
}
@keyframes textColorShift {
0% {
color: #2c3e50;
text-shadow: 1px 1px 2px rgba(255, 255, 255, 0.8);
}
25% {
color: #1a252f;
text-shadow: 2px 2px 4px rgba(255, 255, 255, 0.9);
}
50% {
color: #34495e;
text-shadow: 1px 1px 3px rgba(255, 255, 255, 0.7);
}
75% {
color: #2c3e50;
text-shadow: 2px 2px 4px rgba(255, 255, 255, 0.8);
}
100% {
color: #2c3e50;
text-shadow: 1px 1px 2px rgba(255, 255, 255, 0.8);
}
}
/* 容器 */
@@ -31,7 +55,7 @@ body {
}
}
/* 玻璃拟态基础样式 */
/* 玻璃拟态基础样式 - 动态调节版本 */
.glass-morphism {
background: rgba(255, 255, 255, 0.15);
backdrop-filter: blur(20px);
@@ -41,9 +65,48 @@ body {
0 8px 32px 0 rgba(31, 38, 135, 0.15),
inset 0 1px 0 rgba(255, 255, 255, 0.3);
border-radius: 20px;
animation: glassColorShift 25s ease infinite;
}
/* 头部 */
@keyframes glassColorShift {
0% {
background: rgba(255, 255, 255, 0.15);
border-color: rgba(255, 255, 255, 0.2);
box-shadow:
0 8px 32px 0 rgba(31, 38, 135, 0.15),
inset 0 1px 0 rgba(255, 255, 255, 0.3);
}
25% {
background: rgba(255, 255, 255, 0.25);
border-color: rgba(255, 255, 255, 0.35);
box-shadow:
0 8px 32px 0 rgba(31, 38, 135, 0.25),
inset 0 1px 0 rgba(255, 255, 255, 0.5);
}
50% {
background: rgba(255, 255, 255, 0.18);
border-color: rgba(255, 255, 255, 0.25);
box-shadow:
0 8px 32px 0 rgba(31, 38, 135, 0.18),
inset 0 1px 0 rgba(255, 255, 255, 0.35);
}
75% {
background: rgba(255, 255, 255, 0.22);
border-color: rgba(255, 255, 255, 0.3);
box-shadow:
0 8px 32px 0 rgba(31, 38, 135, 0.2),
inset 0 1px 0 rgba(255, 255, 255, 0.4);
}
100% {
background: rgba(255, 255, 255, 0.15);
border-color: rgba(255, 255, 255, 0.2);
box-shadow:
0 8px 32px 0 rgba(31, 38, 135, 0.15),
inset 0 1px 0 rgba(255, 255, 255, 0.3);
}
}
/* 头部 - 动态调节版本 */
.header {
text-align: center;
margin-bottom: 40px;
@@ -58,6 +121,45 @@ body {
inset 0 1px 0 rgba(255, 255, 255, 0.4);
position: relative;
overflow: hidden;
animation: headerColorShift 25s ease infinite;
}
@keyframes headerColorShift {
0% {
background: rgba(255, 255, 255, 0.1);
border-color: rgba(255, 255, 255, 0.2);
box-shadow:
0 8px 32px 0 rgba(31, 38, 135, 0.2),
inset 0 1px 0 rgba(255, 255, 255, 0.4);
}
25% {
background: rgba(255, 255, 255, 0.2);
border-color: rgba(255, 255, 255, 0.35);
box-shadow:
0 8px 32px 0 rgba(31, 38, 135, 0.3),
inset 0 1px 0 rgba(255, 255, 255, 0.6);
}
50% {
background: rgba(255, 255, 255, 0.15);
border-color: rgba(255, 255, 255, 0.25);
box-shadow:
0 8px 32px 0 rgba(31, 38, 135, 0.22),
inset 0 1px 0 rgba(255, 255, 255, 0.45);
}
75% {
background: rgba(255, 255, 255, 0.18);
border-color: rgba(255, 255, 255, 0.3);
box-shadow:
0 8px 32px 0 rgba(31, 38, 135, 0.25),
inset 0 1px 0 rgba(255, 255, 255, 0.5);
}
100% {
background: rgba(255, 255, 255, 0.1);
border-color: rgba(255, 255, 255, 0.2);
box-shadow:
0 8px 32px 0 rgba(31, 38, 135, 0.2),
inset 0 1px 0 rgba(255, 255, 255, 0.4);
}
}
.header::before {
@@ -102,30 +204,77 @@ body {
.title {
font-size: 3.2em;
font-weight: 800;
color: rgba(255, 255, 255, 0.95);
color: #2c3e50;
margin-bottom: 10px;
text-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
text-shadow: 1px 1px 2px rgba(255, 255, 255, 0.8), 0 0 4px rgba(255, 255, 255, 0.6);
letter-spacing: 2px;
animation: titleGlow 4s ease-in-out infinite alternate;
animation: titleGlow 4s ease-in-out infinite alternate, titleColorShift 25s ease infinite;
}
@keyframes titleColorShift {
0% {
color: #2c3e50;
text-shadow: 1px 1px 2px rgba(255, 255, 255, 0.8), 0 0 4px rgba(255, 255, 255, 0.6);
}
25% {
color: #3498db;
text-shadow: 1px 1px 3px rgba(255, 255, 255, 0.9), 0 0 6px rgba(52, 152, 219, 0.4);
}
50% {
color: #e74c3c;
text-shadow: 1px 1px 2px rgba(255, 255, 255, 0.7), 0 0 5px rgba(231, 76, 60, 0.3);
}
75% {
color: #f39c12;
text-shadow: 1px 1px 3px rgba(255, 255, 255, 0.8), 0 0 6px rgba(243, 156, 18, 0.4);
}
100% {
color: #2c3e50;
text-shadow: 1px 1px 2px rgba(255, 255, 255, 0.8), 0 0 4px rgba(255, 255, 255, 0.6);
}
}
@keyframes titleGlow {
0% {
text-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
color: rgba(255, 255, 255, 0.95);
text-shadow: 1px 1px 2px rgba(255, 255, 255, 0.8), 0 0 4px rgba(255, 255, 255, 0.6);
color: #2c3e50;
}
100% {
text-shadow: 0 0 20px rgba(255, 255, 255, 0.8), 0 2px 10px rgba(0, 0, 0, 0.3);
color: rgba(255, 255, 255, 1);
text-shadow: 2px 2px 4px rgba(255, 255, 255, 0.9), 0 0 8px rgba(255, 255, 255, 0.7);
color: #3498db;
}
}
.subtitle {
font-size: 1.3em;
color: rgba(255, 255, 255, 0.8);
color: #7f8c8d;
margin-bottom: 30px;
font-weight: 500;
text-shadow: 0 1px 5px rgba(0, 0, 0, 0.2);
font-weight: 600;
text-shadow: 0 1px 2px rgba(255, 255, 255, 0.8);
animation: subtitleColorShift 25s ease infinite;
}
@keyframes subtitleColorShift {
0% {
color: #7f8c8d;
text-shadow: 0 1px 2px rgba(255, 255, 255, 0.8);
}
25% {
color: #9b59b6;
text-shadow: 0 1px 3px rgba(255, 255, 255, 0.9);
}
50% {
color: #e67e22;
text-shadow: 0 1px 2px rgba(255, 255, 255, 0.7);
}
75% {
color: #27ae60;
text-shadow: 0 1px 3px rgba(255, 255, 255, 0.8);
}
100% {
color: #7f8c8d;
text-shadow: 0 1px 2px rgba(255, 255, 255, 0.8);
}
}
/* 日期选择器 */
@@ -149,10 +298,34 @@ body {
display: flex;
align-items: center;
gap: 8px;
color: rgba(255, 255, 255, 0.9);
color: #0f1419;
font-weight: 600;
font-size: 1em;
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.6);
animation: labelColorShift 25s ease infinite;
}
@keyframes labelColorShift {
0% {
color: #0f1419;
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.6);
}
25% {
color: #1a252f;
text-shadow: 2px 2px 3px rgba(0, 0, 0, 0.7);
}
50% {
color: #2c3e50;
text-shadow: 1px 1px 3px rgba(0, 0, 0, 0.5);
}
75% {
color: #0f1419;
text-shadow: 2px 2px 2px rgba(0, 0, 0, 0.6);
}
100% {
color: #0f1419;
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.6);
}
}
.label-icon {
@@ -166,13 +339,42 @@ body {
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 15px;
padding: 12px 16px;
color: rgba(255, 255, 255, 0.95);
color: #0a0f14;
font-size: 1em;
font-weight: 500;
transition: all 0.3s ease;
box-shadow:
0 4px 15px rgba(31, 38, 135, 0.1),
inset 0 1px 0 rgba(255, 255, 255, 0.2);
animation: inputColorShift 25s ease infinite;
}
@keyframes inputColorShift {
0% {
background: rgba(255, 255, 255, 0.1);
border-color: rgba(255, 255, 255, 0.3);
color: #0a0f14;
}
25% {
background: rgba(255, 255, 255, 0.2);
border-color: rgba(255, 255, 255, 0.4);
color: #1a252f;
}
50% {
background: rgba(255, 255, 255, 0.15);
border-color: rgba(255, 255, 255, 0.35);
color: #2c3e50;
}
75% {
background: rgba(255, 255, 255, 0.18);
border-color: rgba(255, 255, 255, 0.38);
color: #0a0f14;
}
100% {
background: rgba(255, 255, 255, 0.1);
border-color: rgba(255, 255, 255, 0.3);
color: #0a0f14;
}
}
.date-input:focus {
@@ -196,7 +398,7 @@ body {
background: rgba(255, 255, 255, 0.15);
backdrop-filter: blur(15px);
-webkit-backdrop-filter: blur(15px);
color: rgba(255, 255, 255, 0.95);
color: #0a0f14;
border: 1px solid rgba(255, 255, 255, 0.3);
padding: 12px 28px;
border-radius: 20px;
@@ -209,10 +411,39 @@ body {
gap: 8px;
position: relative;
overflow: hidden;
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.5);
box-shadow:
0 4px 15px rgba(31, 38, 135, 0.2),
inset 0 1px 0 rgba(255, 255, 255, 0.3);
animation: buttonColorShift 25s ease infinite;
}
@keyframes buttonColorShift {
0% {
background: rgba(255, 255, 255, 0.15);
border-color: rgba(255, 255, 255, 0.3);
color: #0a0f14;
}
25% {
background: rgba(255, 255, 255, 0.25);
border-color: rgba(255, 255, 255, 0.4);
color: #1a252f;
}
50% {
background: rgba(255, 255, 255, 0.2);
border-color: rgba(255, 255, 255, 0.35);
color: #2c3e50;
}
75% {
background: rgba(255, 255, 255, 0.22);
border-color: rgba(255, 255, 255, 0.38);
color: #0a0f14;
}
100% {
background: rgba(255, 255, 255, 0.15);
border-color: rgba(255, 255, 255, 0.3);
color: #0a0f14;
}
}
.query-btn::before {
@@ -257,7 +488,7 @@ body {
align-items: center;
justify-content: center;
gap: 8px;
color: rgba(255, 255, 255, 0.8);
color: #1a252f;
font-size: 1em;
padding: 10px 20px;
background: rgba(255, 255, 255, 0.08);
@@ -266,7 +497,8 @@ body {
border-radius: 20px;
border: 1px solid rgba(255, 255, 255, 0.15);
display: inline-flex;
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
font-weight: 600;
}
.time-icon {
@@ -477,8 +709,8 @@ body {
.card-title {
font-size: 1.5em;
font-weight: 700;
color: rgba(255, 255, 255, 0.95);
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
color: #0a0f14;
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.6);
}
.card-content {
@@ -512,16 +744,16 @@ body {
.item-label {
font-weight: 600;
color: rgba(255, 255, 255, 0.9);
color: #1a252f;
min-width: 80px;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
}
.item-value {
font-weight: 700;
color: rgba(255, 255, 255, 0.95);
color: #0a0f14;
font-size: 1.1em;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
}
/* 错误信息 */
@@ -558,14 +790,15 @@ body {
.error-content h3 {
font-size: 1.6em;
color: rgba(255, 255, 255, 0.95);
color: #0a0f14;
margin: 0;
font-weight: 700;
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.6);
}
.error-content p {
color: rgba(255, 255, 255, 0.8);
color: #1a252f;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.4);
font-size: 1.1em;
margin: 0;
line-height: 1.6;
@@ -576,7 +809,7 @@ body {
background: rgba(255, 255, 255, 0.15);
backdrop-filter: blur(15px);
-webkit-backdrop-filter: blur(15px);
color: rgba(255, 255, 255, 0.95);
color: #0a0f14;
border: 1px solid rgba(255, 255, 255, 0.3);
padding: 12px 25px;
border-radius: 20px;
@@ -587,7 +820,7 @@ body {
display: inline-flex;
align-items: center;
gap: 8px;
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.6);
box-shadow:
0 4px 15px rgba(31, 38, 135, 0.2),
inset 0 1px 0 rgba(255, 255, 255, 0.3);
@@ -643,9 +876,9 @@ body {
.tip-card h3 {
font-size: 1.4em;
font-weight: 700;
color: rgba(255, 255, 255, 0.95);
color: #0a0f14;
margin-bottom: 20px;
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.6);
}
.tip-card ul {
@@ -656,7 +889,7 @@ body {
}
.tip-card li {
color: rgba(255, 255, 255, 0.9);
color: #1a252f;
font-size: 1em;
padding: 10px 15px;
background: rgba(255, 255, 255, 0.08);
@@ -665,7 +898,7 @@ body {
border-radius: 10px;
border: 1px solid rgba(255, 255, 255, 0.15);
transition: all 0.3s ease;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.4);
}
.tip-card li:hover {
@@ -887,6 +1120,35 @@ body {
padding: 8px 12px;
font-size: 0.9em;
}
/* 手机端性能优化 - 减少动画 */
.title {
animation: none;
}
.subtitle {
animation: none;
}
.input-label {
animation: none;
}
.date-input {
animation: none;
}
.query-btn {
animation: none;
}
.card-icon {
animation: none;
}
.tip-icon {
animation: none;
}
}
/* 超小屏幕适配 (480px以下) */
@@ -1004,6 +1266,35 @@ body {
display: none;
}
/* 禁用复杂动画以提升性能 */
.title {
animation: none;
}
.subtitle {
animation: none;
}
.input-label {
animation: none;
}
.date-input {
animation: none;
}
.query-btn {
animation: none;
}
.card-icon {
animation: none;
}
.tip-icon {
animation: none;
}
.loading-content {
gap: 15px;
}
@@ -1047,12 +1338,14 @@ body {
.hour-name {
font-size: 14px;
font-weight: 600;
color: #ffffff;
color: #2c3e50;
text-align: center;
margin-bottom: 8px;
padding: 4px 8px;
background: rgba(255, 255, 255, 0.1);
background: rgba(255, 255, 255, 0.8);
border-radius: 6px;
text-shadow: 0 1px 2px rgba(255, 255, 255, 0.5);
border: 1px solid rgba(255, 255, 255, 0.9);
}
.hour-content {

View File

@@ -8,6 +8,44 @@
<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>

View File

@@ -168,15 +168,6 @@
</button>
</div>
</div>
<div class="result-item">
<label>Gzip 解压</label>
<div class="result-value" id="gzipDecompressResult">
<span class="placeholder">等待处理...</span>
<button class="copy-btn" data-target="gzipDecompressResult">
<i class="fas fa-copy"></i>
</button>
</div>
</div>
<div class="result-item">
<label>Deflate 压缩</label>
<div class="result-value" id="deflateCompressResult">

View File

@@ -23,7 +23,6 @@ const resultElements = {
urlEncode: document.getElementById('urlEncodeResult'),
urlDecode: document.getElementById('urlDecodeResult'),
gzipCompress: document.getElementById('gzipCompressResult'),
gzipDecompress: document.getElementById('gzipDecompressResult'),
deflateCompress: document.getElementById('deflateCompressResult'),
brotliCompress: document.getElementById('brotliCompressResult')
};
@@ -142,28 +141,43 @@ function displayResults(data) {
// Base64编码
if (data.base64) {
updateResultElement('base64Encode', data.base64.encode || '不可用');
updateResultElement('base64Decode', data.base64.decode || '不可用');
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.encode || '不可用');
updateResultElement('urlDecode', data.url.decode || '不可用');
updateResultElement('urlEncode', data.url.encoded || '不可用');
updateResultElement('urlDecode', data.url.decoded || '不可用');
}
// 压缩结果
// 压缩结果(仅显示压缩,不显示解压)
if (data.gzip) {
updateResultElement('gzipCompress', data.gzip.compress || '不可用');
updateResultElement('gzipDecompress', data.gzip.decompress || '不可用');
updateResultElement('gzipCompress', data.gzip.encoded || '不可用');
}
if (data.deflate) {
updateResultElement('deflateCompress', data.deflate.compress || '不可用');
updateResultElement('deflateCompress', data.deflate.encoded || '不可用');
}
if (data.brotli) {
updateResultElement('brotliCompress', data.brotli.compress || '不可用');
updateResultElement('brotliCompress', data.brotli.encoded || '不可用');
}
} catch (error) {

View File

@@ -1,33 +1,35 @@
{
"code": 200,
"message": "处理成功",
"message": "获取成功。数据来自官方/权威源头,以确保稳定与实时。开源地址 https://github.com/vikiboss/60s反馈群 595941841",
"data": {
"source": "你好👋",
"md5": "a1b2c3d4e5f6789012345678901234567",
"source": "hello",
"md5": "5d41402abc4b2a76b9719d911017c592",
"sha": {
"sha1": "da39a3ee5e6b4b0d3255bfef95601890afd80709",
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"sha512": "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e"
"sha1": "aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d",
"sha256": "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824",
"sha512": "9b71d224bd62f3785d96d46ad3ea3d73319bfbc2890caadae2dff72519673ca72323c3d99ba5c11d7c7acc6e14b8c5da0c4663475c2e5c3adef46f73bcdec043"
},
"base64": {
"encode": "5L2g5aW9",
"decode": "你好"
"encoded": "aGVsbG8=",
"decoded": ""
},
"url": {
"encode": "%E4%BD%A0%E5%A5%BD%F0%9F%91%8B",
"decode": "你好👋"
"encoded": "hello",
"decoded": "hello"
},
"gzip": {
"compress": "H4sIAAAAAAAAA...(压缩后的数据)",
"decompress": "你好👋"
"encoded": "1f8b0800000000000003cb48cdc9c9070086a6103605000000",
"decoded": ""
},
"deflate": {
"compress": "eJwrz8kvTUlMy...(压缩后的数据)",
"decompress": "你好👋"
"encoded": "789ccb48cdc9c90700062c0215",
"decoded": ""
},
"brotli": {
"compress": "CwWAaGVsbG8g...(压缩后的数据)",
"decompress": "你好👋"
"encoded": "0b028068656c6c6f03",
"decoded": ""
}
}
}
}
API encoded/decoded encode/decode

View File

@@ -135,8 +135,8 @@
<div id="og-image" class="info-value url-value">-</div>
</div>
<div class="info-item">
<label>图片尺寸</label>
<div id="og-image-size" class="info-value">-</div>
<label>图片描述</label>
<div id="og-image-alt" class="info-value">-</div>
</div>
</div>
</div>
@@ -161,8 +161,12 @@
<div id="og-locale" class="info-value">-</div>
</div>
<div class="info-item">
<label>字符编码</label>
<div id="og-charset" class="info-value">-</div>
<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>

View File

@@ -124,6 +124,7 @@ class OGAnalyzer {
this.currentUrl = url;
this.isAnalyzing = true;
this.startTime = Date.now(); // 记录开始时间
this.showLoading();
this.hideError();
this.hideResults();
@@ -185,23 +186,36 @@ class OGAnalyzer {
const resultsElement = document.getElementById('results');
const ogCard = document.getElementById('og-card');
// 基础信息
this.updateElement('og-title', data.title || '未获取到标题');
this.updateElement('og-description', data.description || '未获取到描述');
this.updateElement('og-url', data.url || this.currentUrl);
this.updateElement('og-site-name', data.site_name || '未知站点');
this.updateElement('og-type', data.type || 'website');
// 检查是否有有效数据 - 放宽检查条件,只要有任何非空字段就显示
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.updateImageElement('og-image', data.image);
this.updateElement('og-image-alt', data.image_alt || '图片描述不可用');
this.updateImageElementWithVisibility('og-image', data.image);
this.updateElementWithVisibility('og-image-alt', data.image_alt, '图片描述');
// 技术信息
this.updateElement('og-locale', data.locale || '未指定');
this.updateElement('og-updated-time', this.formatDate(data.updated_time));
this.updateElement('response-time', `${Date.now() - this.startTime}ms`);
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');
// 添加动画效果
@@ -219,6 +233,21 @@ class OGAnalyzer {
}
}
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) {
@@ -240,6 +269,30 @@ class OGAnalyzer {
}
}
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 {
@@ -292,6 +345,7 @@ class OGAnalyzer {
hideResults() {
const resultsElement = document.getElementById('results');
resultsElement.style.display = 'none';
resultsElement.classList.remove('active');
// 重置动画状态
@@ -358,6 +412,7 @@ class OGAnalyzer {
urlInput.value = '';
urlInput.classList.remove('error');
resultsElement.style.display = 'none';
resultsElement.classList.remove('active');
errorElement.classList.remove('active');
@@ -367,6 +422,13 @@ class OGAnalyzer {
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'));

View File

@@ -1,5 +1,5 @@
// 本地后端API接口
const LOCAL_API_BASE = 'http://localhost:5000/api/60s';
const LOCAL_API_BASE = 'https://infogenie.api.shumengya.top/api/60s';
// API接口列表备用
const API_ENDPOINTS = [

View File

@@ -0,0 +1,2 @@
# 生产环境API配置
REACT_APP_API_URL=https://infogenie.api.shumengya.top

View File

@@ -44,6 +44,5 @@
"last 1 firefox version",
"last 1 safari version"
]
},
"proxy": "http://localhost:5000"
}
}

48
frontend/react-app/public/sw.js vendored Normal file
View File

@@ -0,0 +1,48 @@
// Service Worker for InfoGenie App
const CACHE_NAME = 'infogenie-cache-v1';
const urlsToCache = [
'/',
'/index.html',
'/manifest.json'
];
// 安装Service Worker
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => {
console.log('Opened cache');
return cache.addAll(urlsToCache);
})
);
});
// 拦截请求并从缓存中响应
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(response => {
// 如果找到缓存的响应,则返回缓存
if (response) {
return response;
}
return fetch(event.request);
})
);
});
// 更新Service Worker
self.addEventListener('activate', event => {
const cacheWhitelist = [CACHE_NAME];
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(cacheName => {
if (cacheWhitelist.indexOf(cacheName) === -1) {
return caches.delete(cacheName);
}
})
);
})
);
});

View File

@@ -9,3 +9,16 @@ root.render(
<App />
</React.StrictMode>
);
// 注册Service Worker
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js')
.then(registration => {
console.log('ServiceWorker registration successful with scope: ', registration.scope);
})
.catch(error => {
console.error('ServiceWorker registration failed: ', error);
});
});
}

View File

@@ -3,7 +3,7 @@ import { useNavigate } from 'react-router-dom';
import styled from 'styled-components';
import { FiCpu, FiUser, FiExternalLink, FiArrowLeft } from 'react-icons/fi';
import { useUser } from '../contexts/UserContext';
import axios from 'axios';
import api from '../utils/api';
const AiContainer = styled.div`
min-height: calc(100vh - 140px);
@@ -262,7 +262,7 @@ const AiModelPage = () => {
const fetchApps = async () => {
try {
setLoadingApps(true);
const response = await axios.get('/api/aimodelapp/scan-directories');
const response = await api.get('/api/aimodelapp/scan-directories');
if (response.data.success) {
setApps(response.data.apps);
} else {
@@ -278,7 +278,8 @@ const AiModelPage = () => {
const handleLaunchApp = (app) => {
// 将相对路径转换为完整的服务器地址
const fullLink = `http://localhost:5000${app.link}`;
const baseUrl = process.env.REACT_APP_API_URL || 'https://infogenie.api.shumengya.top';
const fullLink = `${baseUrl}${app.link}`;
setEmbeddedApp({ ...app, link: fullLink });
};

View File

@@ -238,81 +238,26 @@ const Api60sPage = () => {
// 从后端API获取目录结构
const scanDirectories = async () => {
try {
const response = await fetch('http://localhost:5000/api/60s/scan-directories');
// 使用环境变量中配置的API URL
const baseUrl = process.env.REACT_APP_API_URL || 'https://infogenie.api.shumengya.top';
const apiUrl = `${baseUrl}/api/60s/scan-directories`;
console.log('正在请求API目录结构:', apiUrl);
const response = await fetch(apiUrl);
if (response.ok) {
const data = await response.json();
return data;
}
} catch (error) {
console.warn('无法从后端获取目录结构,使用前端扫描方式');
console.warn('无法从后端获取目录结构:', error);
}
return null;
};
// 前端扫描方式(备用)
const frontendScan = async () => {
const categories = [];
for (const [categoryName, config] of Object.entries(categoryConfig)) {
const apis = [];
// 尝试访问已知的模块列表(只包含实际存在的模块)
const knownModules = {
'热搜榜单': ['抖音热搜榜'],
'日更资讯': [],
'实用功能': [],
'娱乐消遣': []
};
// 前端扫描方式已移除
const moduleNames = knownModules[categoryName] || [];
for (let i = 0; i < moduleNames.length; i++) {
const moduleName = moduleNames[i];
try {
const indexPath = `/60sapi/${categoryName}/${moduleName}/index.html`;
const fullUrl = `http://localhost:5000${indexPath}`;
const response = await fetch(fullUrl, { method: 'HEAD' });
if (response.ok) {
// 获取页面标题
const htmlResponse = await fetch(fullUrl);
const html = await htmlResponse.text();
const titleMatch = html.match(/<title>(.*?)<\/title>/i);
const title = titleMatch ? titleMatch[1].trim() : moduleName;
apis.push({
title,
description: `${moduleName}相关功能`,
link: fullUrl,
status: 'active',
color: gradientColors[i % gradientColors.length]
});
}
} catch (error) {
// 忽略访问失败的模块
}
}
if (apis.length > 0) {
categories.push({
title: categoryName,
icon: config.icon,
color: config.color,
apis
});
}
}
return categories;
};
// 首先尝试后端扫描,失败则使用前端扫描
// 只使用后端扫描
const backendResult = await scanDirectories();
if (backendResult && backendResult.success) {
return backendResult.categories || [];
} else {
return await frontendScan();
}
return backendResult && backendResult.success ? backendResult.categories || [] : [];
} catch (error) {
console.error('扫描API模块时出错:', error);

View File

@@ -1,7 +1,7 @@
import React, { useState, useEffect } from 'react';
import styled from 'styled-components';
import { FiGrid, FiPlay, FiExternalLink, FiArrowLeft } from 'react-icons/fi';
import axios from 'axios';
import api from '../utils/api';
const GameContainer = styled.div`
min-height: calc(100vh - 140px);
@@ -233,7 +233,7 @@ const SmallGamePage = () => {
const fetchGames = async () => {
try {
setLoading(true);
const response = await axios.get('/api/smallgame/scan-directories');
const response = await api.get('/api/smallgame/scan-directories');
if (response.data.success) {
setGames(response.data.games);
} else {
@@ -249,7 +249,8 @@ const SmallGamePage = () => {
const handlePlayGame = (game) => {
// 将相对路径转换为完整的服务器地址
const fullLink = `http://localhost:5000${game.link}`;
const baseUrl = process.env.REACT_APP_API_URL || 'https://infogenie.api.shumengya.top';
const fullLink = `${baseUrl}${game.link}`;
setEmbeddedGame({ ...game, link: fullLink });
};

View File

@@ -3,7 +3,7 @@ import toast from 'react-hot-toast';
// 创建axios实例
const api = axios.create({
baseURL: process.env.REACT_APP_API_URL || '/api',
baseURL: process.env.REACT_APP_API_URL || 'https://infogenie.api.shumengya.top',
timeout: 10000,
withCredentials: true, // 支持携带cookie
headers: {
@@ -11,6 +11,9 @@ const api = axios.create({
}
});
// 打印当前使用的API URL便于调试
console.log('API Base URL:', process.env.REACT_APP_API_URL || 'https://infogenie.api.shumengya.top');
// 请求拦截器
api.interceptors.request.use(
(config) => {
@@ -44,63 +47,63 @@ api.interceptors.response.use(
// 认证相关API
export const authAPI = {
// 发送验证码
sendVerification: (data) => api.post('/auth/send-verification', data),
sendVerification: (data) => api.post('/api/auth/send-verification', data),
// 验证验证码
verifyCode: (data) => api.post('/auth/verify-code', data),
verifyCode: (data) => api.post('/api/auth/verify-code', data),
// 登录
login: (credentials) => api.post('/auth/login', credentials),
login: (credentials) => api.post('/api/auth/login', credentials),
// 注册
register: (userData) => api.post('/auth/register', userData),
register: (userData) => api.post('/api/auth/register', userData),
// 登出
logout: () => api.post('/auth/logout'),
logout: () => api.post('/api/auth/logout'),
// 检查登录状态
checkLogin: () => api.get('/auth/check'),
checkLogin: () => api.get('/api/auth/check'),
};
// 用户相关API
export const userAPI = {
// 获取用户资料
getProfile: () => api.get('/user/profile'),
getProfile: () => api.get('/api/user/profile'),
// 修改密码
changePassword: (passwordData) => api.post('/user/change-password', passwordData),
changePassword: (passwordData) => api.post('/api/user/change-password', passwordData),
// 获取用户统计
getStats: () => api.get('/user/stats'),
getStats: () => api.get('/api/user/stats'),
// 删除账户
deleteAccount: (password) => api.post('/user/delete', { password }),
deleteAccount: (password) => api.post('/api/user/delete', { password }),
};
// 60s API相关接口
export const api60s = {
// 抖音热搜
getDouyinHot: () => api.get('/60s/douyin'),
getDouyinHot: () => api.get('/api/60s/douyin'),
// 微博热搜
getWeiboHot: () => api.get('/60s/weibo'),
getWeiboHot: () => api.get('/api/60s/weibo'),
// 猫眼票房
getMaoyanBoxOffice: () => api.get('/60s/maoyan'),
getMaoyanBoxOffice: () => api.get('/api/60s/maoyan'),
// 60秒读懂世界
get60sNews: () => api.get('/60s/60s'),
get60sNews: () => api.get('/api/60s/60s'),
// 必应壁纸
getBingWallpaper: () => api.get('/60s/bing-wallpaper'),
getBingWallpaper: () => api.get('/api/60s/bing-wallpaper'),
// 天气信息
getWeather: (city = '北京') => api.get(`/60s/weather?city=${encodeURIComponent(city)}`),
getWeather: (city = '北京') => api.get(`/api/60s/weather?city=${encodeURIComponent(city)}`),
};
// 健康检查
export const healthAPI = {
check: () => api.get('/health'),
check: () => api.get('/api/health'),
};
export default api;

72
nginx-config-example.conf Normal file
View File

@@ -0,0 +1,72 @@
# Nginx配置示例 - InfoGenie部署
# 前端配置 - infogenie.shumengya.top
server {
listen 80;
server_name infogenie.shumengya.top;
# 重定向HTTP到HTTPS
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl;
server_name infogenie.shumengya.top;
# SSL证书配置
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
# 前端静态文件目录
root /var/www/infogenie;
index index.html;
# 处理React路由
location / {
try_files $uri $uri/ /index.html;
}
# 安全相关配置
add_header X-Frame-Options "SAMEORIGIN";
add_header X-XSS-Protection "1; mode=block";
add_header X-Content-Type-Options "nosniff";
}
# 后端配置 - infogenie.api.shumengya.top
server {
listen 80;
server_name infogenie.api.shumengya.top;
# 重定向HTTP到HTTPS
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl;
server_name infogenie.api.shumengya.top;
# SSL证书配置
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
# 反向代理到后端服务
location / {
proxy_pass http://127.0.0.1:5000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# 安全相关配置
add_header X-Frame-Options "SAMEORIGIN";
add_header X-XSS-Protection "1; mode=block";
add_header X-Content-Type-Options "nosniff";
# 允许较大的上传文件
client_max_body_size 10M;
}