Merge branch 'main' of https://github.com/shumengya/InfoGenie
This commit is contained in:
59
README.md
59
README.md
@@ -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
14
backend/.env.production
Normal 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
|
||||
@@ -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
27
backend/deploy.bat
Normal 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
|
||||
@@ -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
6
build_frontend.bat
Normal file
@@ -0,0 +1,6 @@
|
||||
@echo off
|
||||
cd /d "e:\Python\InfoGenie\frontend\react-app"
|
||||
npm run build
|
||||
|
||||
npx serve -s build
|
||||
pause
|
||||
@@ -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';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
[
|
||||
"https://60s.viki.moe/v2/ip"
|
||||
]
|
||||
]
|
||||
@@ -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服务
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
@@ -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>
|
||||
|
||||
@@ -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'));
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
2
frontend/react-app/.env.production
Normal file
2
frontend/react-app/.env.production
Normal file
@@ -0,0 +1,2 @@
|
||||
# 生产环境API配置
|
||||
REACT_APP_API_URL=https://infogenie.api.shumengya.top
|
||||
@@ -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
48
frontend/react-app/public/sw.js
vendored
Normal 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);
|
||||
}
|
||||
})
|
||||
);
|
||||
})
|
||||
);
|
||||
});
|
||||
13
frontend/react-app/src/index.js
vendored
13
frontend/react-app/src/index.js
vendored
@@ -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);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
7
frontend/react-app/src/pages/AiModelPage.js
vendored
7
frontend/react-app/src/pages/AiModelPage.js
vendored
@@ -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 });
|
||||
};
|
||||
|
||||
|
||||
73
frontend/react-app/src/pages/Api60sPage.js
vendored
73
frontend/react-app/src/pages/Api60sPage.js
vendored
@@ -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);
|
||||
|
||||
@@ -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 });
|
||||
};
|
||||
|
||||
|
||||
39
frontend/react-app/src/utils/api.js
vendored
39
frontend/react-app/src/utils/api.js
vendored
@@ -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
72
nginx-config-example.conf
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user