Merge branch 'main' of https://github.com/shumengya/InfoGenie
This commit is contained in:
@@ -1,543 +0,0 @@
|
|||||||
import smtplib
|
|
||||||
from email.mime.text import MIMEText
|
|
||||||
from email.mime.multipart import MIMEMultipart
|
|
||||||
from email.mime.application import MIMEApplication
|
|
||||||
from email.header import Header
|
|
||||||
import random
|
|
||||||
import string
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
|
|
||||||
# 邮件发送配置
|
|
||||||
SENDER_EMAIL = '3205788256@qq.com' # 发件人邮箱
|
|
||||||
SENDER_AUTH_CODE = 'szcaxvbftusqddhi' # 授权码
|
|
||||||
SMTP_SERVER = 'smtp.qq.com' # QQ邮箱SMTP服务器
|
|
||||||
SMTP_PORT = 465 # QQ邮箱SSL端口
|
|
||||||
|
|
||||||
# 验证码缓存文件
|
|
||||||
VERIFICATION_CACHE_FILE = os.path.join("config", "verification_codes.json")
|
|
||||||
|
|
||||||
class QQMailAPI:
|
|
||||||
"""QQ邮箱发送邮件API类"""
|
|
||||||
|
|
||||||
def __init__(self, sender_email, authorization_code):
|
|
||||||
"""
|
|
||||||
初始化邮箱配置
|
|
||||||
:param sender_email: 发送方QQ邮箱地址
|
|
||||||
:param authorization_code: QQ邮箱授权码
|
|
||||||
"""
|
|
||||||
self.sender_email = sender_email
|
|
||||||
self.authorization_code = authorization_code
|
|
||||||
self.smtp_server = 'smtp.qq.com'
|
|
||||||
self.smtp_port = 465 # SSL端口
|
|
||||||
|
|
||||||
# 发送纯文本邮件
|
|
||||||
def send_text_email(self, receiver_email, subject, content, cc_emails=None):
|
|
||||||
"""
|
|
||||||
发送纯文本邮件
|
|
||||||
:param receiver_email: 接收方邮箱地址(单个)
|
|
||||||
:param subject: 邮件主题
|
|
||||||
:param content: 邮件正文内容
|
|
||||||
:param cc_emails: 抄送邮箱列表
|
|
||||||
:return: 发送成功返回True,失败返回False
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
# 创建邮件对象
|
|
||||||
message = MIMEText(content, 'plain', 'utf-8')
|
|
||||||
message['From'] = Header(self.sender_email, 'utf-8')
|
|
||||||
message['To'] = Header(receiver_email, 'utf-8')
|
|
||||||
message['Subject'] = Header(subject, 'utf-8')
|
|
||||||
|
|
||||||
# 添加抄送
|
|
||||||
if cc_emails:
|
|
||||||
message['Cc'] = Header(",".join(cc_emails), 'utf-8')
|
|
||||||
all_receivers = [receiver_email] + cc_emails
|
|
||||||
else:
|
|
||||||
all_receivers = [receiver_email]
|
|
||||||
|
|
||||||
# 连接SMTP服务器并发送邮件
|
|
||||||
with smtplib.SMTP_SSL(self.smtp_server, self.smtp_port) as server:
|
|
||||||
server.login(self.sender_email, self.authorization_code)
|
|
||||||
server.sendmail(self.sender_email, all_receivers, message.as_string())
|
|
||||||
|
|
||||||
print(f"邮件发送成功:主题='{subject}', 收件人='{receiver_email}'")
|
|
||||||
return True
|
|
||||||
except Exception as e:
|
|
||||||
print(f"邮件发送失败:{str(e)}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
# 发送HTML格式邮件,可带附件
|
|
||||||
def send_html_email(self, receiver_email, subject, html_content, cc_emails=None, attachments=None):
|
|
||||||
"""
|
|
||||||
发送HTML格式邮件,可带附件
|
|
||||||
:param receiver_email: 接收方邮箱地址(单个)
|
|
||||||
:param subject: 邮件主题
|
|
||||||
:param html_content: HTML格式的邮件正文
|
|
||||||
:param cc_emails: 抄送邮箱列表
|
|
||||||
:param attachments: 附件文件路径列表
|
|
||||||
:return: 发送成功返回True,失败返回False
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
# 创建带附件的邮件对象
|
|
||||||
message = MIMEMultipart()
|
|
||||||
message['From'] = Header(self.sender_email, 'utf-8')
|
|
||||||
message['To'] = Header(receiver_email, 'utf-8')
|
|
||||||
message['Subject'] = Header(subject, 'utf-8')
|
|
||||||
|
|
||||||
# 添加抄送
|
|
||||||
if cc_emails:
|
|
||||||
message['Cc'] = Header(",".join(cc_emails), 'utf-8')
|
|
||||||
all_receivers = [receiver_email] + cc_emails
|
|
||||||
else:
|
|
||||||
all_receivers = [receiver_email]
|
|
||||||
|
|
||||||
# 添加HTML正文
|
|
||||||
message.attach(MIMEText(html_content, 'html', 'utf-8'))
|
|
||||||
|
|
||||||
# 添加附件
|
|
||||||
if attachments:
|
|
||||||
for file_path in attachments:
|
|
||||||
try:
|
|
||||||
with open(file_path, 'rb') as file:
|
|
||||||
attachment = MIMEApplication(file.read(), _subtype="octet-stream")
|
|
||||||
attachment.add_header('Content-Disposition', 'attachment', filename=file_path.split("/")[-1])
|
|
||||||
message.attach(attachment)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"添加附件失败 {file_path}: {str(e)}")
|
|
||||||
|
|
||||||
# 连接SMTP服务器并发送邮件
|
|
||||||
with smtplib.SMTP_SSL(self.smtp_server, self.smtp_port) as server:
|
|
||||||
server.login(self.sender_email, self.authorization_code)
|
|
||||||
server.sendmail(self.sender_email, all_receivers, message.as_string())
|
|
||||||
|
|
||||||
print(f"HTML邮件发送成功:主题='{subject}', 收件人='{receiver_email}'")
|
|
||||||
return True
|
|
||||||
except Exception as e:
|
|
||||||
print(f"HTML邮件发送失败:{str(e)}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
class EmailVerification:
|
|
||||||
|
|
||||||
#生成指定长度的随机验证码
|
|
||||||
@staticmethod
|
|
||||||
def generate_verification_code(length=6):
|
|
||||||
"""
|
|
||||||
生成指定长度的随机验证码
|
|
||||||
|
|
||||||
参数:
|
|
||||||
length (int): 验证码长度,默认6位
|
|
||||||
|
|
||||||
返回:
|
|
||||||
str: 生成的验证码
|
|
||||||
"""
|
|
||||||
# 生成包含大写字母和数字的验证码
|
|
||||||
chars = string.ascii_uppercase + string.digits
|
|
||||||
return ''.join(random.choice(chars) for _ in range(length))
|
|
||||||
|
|
||||||
#发送验证码邮件到QQ邮箱
|
|
||||||
@staticmethod
|
|
||||||
def send_verification_email(qq_number, verification_code, email_type="register"):
|
|
||||||
"""
|
|
||||||
发送验证码邮件到QQ邮箱
|
|
||||||
|
|
||||||
参数:
|
|
||||||
qq_number (str): 接收者QQ号
|
|
||||||
verification_code (str): 验证码
|
|
||||||
email_type (str): 邮件类型,"register" 或 "reset_password"
|
|
||||||
|
|
||||||
返回:
|
|
||||||
bool: 发送成功返回True,否则返回False
|
|
||||||
str: 成功或错误信息
|
|
||||||
"""
|
|
||||||
receiver_email = f"{qq_number}@qq.com"
|
|
||||||
|
|
||||||
# 根据邮件类型设置不同的内容
|
|
||||||
if email_type == "reset_password":
|
|
||||||
email_title = "【萌芽农场】密码重置验证码"
|
|
||||||
email_purpose = "重置萌芽农场游戏账号密码"
|
|
||||||
email_color = "#FF6B35" # 橙红色,表示警告性操作
|
|
||||||
else:
|
|
||||||
email_title = "【萌芽农场】注册验证码"
|
|
||||||
email_purpose = "注册萌芽农场游戏账号"
|
|
||||||
email_color = "#4CAF50" # 绿色,表示正常操作
|
|
||||||
|
|
||||||
# 创建邮件内容
|
|
||||||
message = MIMEText(f'''
|
|
||||||
<html>
|
|
||||||
<body>
|
|
||||||
<div style="font-family: Arial, sans-serif; color: #333;">
|
|
||||||
<h2 style="color: {email_color};">萌芽农场 - 邮箱验证码</h2>
|
|
||||||
<p>亲爱的玩家,您好!</p>
|
|
||||||
<p>您正在{email_purpose},您的验证码是:</p>
|
|
||||||
<div style="background-color: #f2f2f2; padding: 10px; font-size: 24px; font-weight: bold; color: {email_color}; text-align: center; margin: 20px 0;">
|
|
||||||
{verification_code}
|
|
||||||
</div>
|
|
||||||
<p>该验证码有效期为5分钟,请勿泄露给他人。</p>
|
|
||||||
<p>如果这不是您本人的操作,请忽略此邮件。</p>
|
|
||||||
<p style="margin-top: 30px; font-size: 12px; color: #999;">
|
|
||||||
本邮件由系统自动发送,请勿直接回复。
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
''', 'html', 'utf-8')
|
|
||||||
|
|
||||||
# 修正From头格式,符合QQ邮箱的要求
|
|
||||||
message['From'] = SENDER_EMAIL
|
|
||||||
message['To'] = receiver_email
|
|
||||||
message['Subject'] = Header(email_title, 'utf-8')
|
|
||||||
|
|
||||||
try:
|
|
||||||
# 使用SSL/TLS连接而不是STARTTLS
|
|
||||||
smtp_obj = smtplib.SMTP_SSL(SMTP_SERVER, 465)
|
|
||||||
smtp_obj.login(SENDER_EMAIL, SENDER_AUTH_CODE)
|
|
||||||
smtp_obj.sendmail(SENDER_EMAIL, [receiver_email], message.as_string())
|
|
||||||
smtp_obj.quit()
|
|
||||||
return True, "验证码发送成功"
|
|
||||||
except Exception as e:
|
|
||||||
return False, f"发送验证码失败: {str(e)}"
|
|
||||||
|
|
||||||
#保存验证码到MongoDB(优先)或缓存文件(备用)
|
|
||||||
@staticmethod
|
|
||||||
def save_verification_code(qq_number, verification_code, expiry_time=300, code_type="register"):
|
|
||||||
"""
|
|
||||||
保存验证码到MongoDB(优先)或缓存文件(备用)
|
|
||||||
|
|
||||||
参数:
|
|
||||||
qq_number (str): QQ号
|
|
||||||
verification_code (str): 验证码
|
|
||||||
expiry_time (int): 过期时间(秒),默认5分钟
|
|
||||||
code_type (str): 验证码类型,"register" 或 "reset_password"
|
|
||||||
|
|
||||||
返回:
|
|
||||||
bool: 保存成功返回True,否则返回False
|
|
||||||
"""
|
|
||||||
import time
|
|
||||||
|
|
||||||
# 优先尝试使用MongoDB
|
|
||||||
try:
|
|
||||||
from SMYMongoDBAPI import SMYMongoDBAPI
|
|
||||||
import os
|
|
||||||
# 根据环境动态选择MongoDB配置
|
|
||||||
if os.path.exists('/.dockerenv') or os.environ.get('PRODUCTION', '').lower() == 'true':
|
|
||||||
environment = "production"
|
|
||||||
else:
|
|
||||||
environment = "test"
|
|
||||||
mongo_api = SMYMongoDBAPI(environment)
|
|
||||||
if mongo_api.is_connected():
|
|
||||||
success = mongo_api.save_verification_code(qq_number, verification_code, expiry_time, code_type)
|
|
||||||
if success:
|
|
||||||
print(f"[验证码系统-MongoDB] 为QQ {qq_number} 保存{code_type}验证码: {verification_code}")
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
print(f"[验证码系统-MongoDB] 保存失败,尝试使用JSON文件")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"[验证码系统-MongoDB] MongoDB保存失败: {str(e)},尝试使用JSON文件")
|
|
||||||
|
|
||||||
# MongoDB失败,使用JSON文件备用
|
|
||||||
# 创建目录(如果不存在)
|
|
||||||
os.makedirs(os.path.dirname(VERIFICATION_CACHE_FILE), exist_ok=True)
|
|
||||||
|
|
||||||
# 读取现有的验证码数据
|
|
||||||
verification_data = {}
|
|
||||||
if os.path.exists(VERIFICATION_CACHE_FILE):
|
|
||||||
try:
|
|
||||||
with open(VERIFICATION_CACHE_FILE, 'r', encoding='utf-8') as file:
|
|
||||||
verification_data = json.load(file)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"读取验证码文件失败: {str(e)}")
|
|
||||||
verification_data = {}
|
|
||||||
|
|
||||||
# 添加新的验证码
|
|
||||||
expire_at = time.time() + expiry_time
|
|
||||||
current_time = time.time()
|
|
||||||
|
|
||||||
# 创建验证码记录,包含更多信息用于调试
|
|
||||||
verification_data[qq_number] = {
|
|
||||||
"code": verification_code,
|
|
||||||
"expire_at": expire_at,
|
|
||||||
"code_type": code_type,
|
|
||||||
"created_at": current_time,
|
|
||||||
"used": False # 新增:标记验证码是否已使用
|
|
||||||
}
|
|
||||||
|
|
||||||
# 保存到文件
|
|
||||||
try:
|
|
||||||
with open(VERIFICATION_CACHE_FILE, 'w', encoding='utf-8') as file:
|
|
||||||
json.dump(verification_data, file, indent=2, ensure_ascii=False)
|
|
||||||
|
|
||||||
print(f"[验证码系统-JSON] 为QQ {qq_number} 保存{code_type}验证码: {verification_code}, 过期时间: {expire_at}")
|
|
||||||
return True
|
|
||||||
except Exception as e:
|
|
||||||
print(f"保存验证码失败: {str(e)}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
#验证用户输入的验证码(优先使用MongoDB)
|
|
||||||
@staticmethod
|
|
||||||
def verify_code(qq_number, input_code, code_type="register"):
|
|
||||||
"""
|
|
||||||
验证用户输入的验证码(优先使用MongoDB)
|
|
||||||
|
|
||||||
参数:
|
|
||||||
qq_number (str): QQ号
|
|
||||||
input_code (str): 用户输入的验证码
|
|
||||||
code_type (str): 验证码类型,"register" 或 "reset_password"
|
|
||||||
|
|
||||||
返回:
|
|
||||||
bool: 验证成功返回True,否则返回False
|
|
||||||
str: 成功或错误信息
|
|
||||||
"""
|
|
||||||
import time
|
|
||||||
|
|
||||||
# 优先尝试使用MongoDB
|
|
||||||
try:
|
|
||||||
from SMYMongoDBAPI import SMYMongoDBAPI
|
|
||||||
import os
|
|
||||||
# 根据环境动态选择MongoDB配置
|
|
||||||
if os.path.exists('/.dockerenv') or os.environ.get('PRODUCTION', '').lower() == 'true':
|
|
||||||
environment = "production"
|
|
||||||
else:
|
|
||||||
environment = "test"
|
|
||||||
mongo_api = SMYMongoDBAPI(environment)
|
|
||||||
if mongo_api.is_connected():
|
|
||||||
success, message = mongo_api.verify_verification_code(qq_number, input_code, code_type)
|
|
||||||
print(f"[验证码系统-MongoDB] QQ {qq_number} 验证结果: {success}, 消息: {message}")
|
|
||||||
return success, message
|
|
||||||
except Exception as e:
|
|
||||||
print(f"[验证码系统-MongoDB] MongoDB验证失败: {str(e)},尝试使用JSON文件")
|
|
||||||
|
|
||||||
# MongoDB失败,使用JSON文件备用
|
|
||||||
# 检查缓存文件是否存在
|
|
||||||
if not os.path.exists(VERIFICATION_CACHE_FILE):
|
|
||||||
print(f"[验证码系统-JSON] QQ {qq_number} 验证失败: 缓存文件不存在")
|
|
||||||
return False, "验证码不存在或已过期"
|
|
||||||
|
|
||||||
# 读取验证码数据
|
|
||||||
try:
|
|
||||||
with open(VERIFICATION_CACHE_FILE, 'r', encoding='utf-8') as file:
|
|
||||||
verification_data = json.load(file)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"[验证码系统-JSON] 读取验证码文件失败: {str(e)}")
|
|
||||||
return False, "验证码数据损坏"
|
|
||||||
|
|
||||||
# 检查该QQ号是否有验证码
|
|
||||||
if qq_number not in verification_data:
|
|
||||||
print(f"[验证码系统-JSON] QQ {qq_number} 验证失败: 没有找到验证码记录")
|
|
||||||
return False, "验证码不存在,请重新获取"
|
|
||||||
|
|
||||||
# 获取存储的验证码信息
|
|
||||||
code_info = verification_data[qq_number]
|
|
||||||
stored_code = code_info.get("code", "")
|
|
||||||
expire_at = code_info.get("expire_at", 0)
|
|
||||||
stored_code_type = code_info.get("code_type", "register")
|
|
||||||
is_used = code_info.get("used", False)
|
|
||||||
created_at = code_info.get("created_at", 0)
|
|
||||||
|
|
||||||
print(f"[验证码系统-JSON] QQ {qq_number} 验证码详情: 存储码={stored_code}, 输入码={input_code}, 类型={stored_code_type}, 已使用={is_used}, 创建时间={created_at}")
|
|
||||||
|
|
||||||
# 检查验证码类型是否匹配
|
|
||||||
if stored_code_type != code_type:
|
|
||||||
print(f"[验证码系统-JSON] QQ {qq_number} 验证失败: 验证码类型不匹配,存储类型={stored_code_type}, 请求类型={code_type}")
|
|
||||||
return False, f"验证码类型不匹配,请重新获取{code_type}验证码"
|
|
||||||
|
|
||||||
# 检查验证码是否已被使用
|
|
||||||
if is_used:
|
|
||||||
print(f"[验证码系统-JSON] QQ {qq_number} 验证失败: 验证码已被使用")
|
|
||||||
return False, "验证码已被使用,请重新获取"
|
|
||||||
|
|
||||||
# 检查验证码是否过期
|
|
||||||
current_time = time.time()
|
|
||||||
if current_time > expire_at:
|
|
||||||
# 移除过期的验证码
|
|
||||||
del verification_data[qq_number]
|
|
||||||
with open(VERIFICATION_CACHE_FILE, 'w', encoding='utf-8') as file:
|
|
||||||
json.dump(verification_data, file, indent=2, ensure_ascii=False)
|
|
||||||
print(f"[验证码系统-JSON] QQ {qq_number} 验证失败: 验证码已过期")
|
|
||||||
return False, "验证码已过期,请重新获取"
|
|
||||||
|
|
||||||
# 验证码比较(不区分大小写)
|
|
||||||
if input_code.upper() == stored_code.upper():
|
|
||||||
# 验证成功,标记为已使用而不是删除
|
|
||||||
verification_data[qq_number]["used"] = True
|
|
||||||
verification_data[qq_number]["used_at"] = current_time
|
|
||||||
|
|
||||||
try:
|
|
||||||
with open(VERIFICATION_CACHE_FILE, 'w', encoding='utf-8') as file:
|
|
||||||
json.dump(verification_data, file, indent=2, ensure_ascii=False)
|
|
||||||
print(f"[验证码系统-JSON] QQ {qq_number} 验证成功: 验证码已标记为已使用")
|
|
||||||
return True, "验证码正确"
|
|
||||||
except Exception as e:
|
|
||||||
print(f"[验证码系统-JSON] 标记验证码已使用时失败: {str(e)}")
|
|
||||||
return True, "验证码正确" # 即使标记失败,验证还是成功的
|
|
||||||
else:
|
|
||||||
print(f"[验证码系统-JSON] QQ {qq_number} 验证失败: 验证码不匹配")
|
|
||||||
return False, "验证码错误"
|
|
||||||
|
|
||||||
#清理过期的验证码和已使用的验证码(优先使用MongoDB)
|
|
||||||
@staticmethod
|
|
||||||
def clean_expired_codes():
|
|
||||||
"""
|
|
||||||
清理过期的验证码和已使用的验证码(优先使用MongoDB)
|
|
||||||
"""
|
|
||||||
import time
|
|
||||||
|
|
||||||
# 优先尝试使用MongoDB
|
|
||||||
try:
|
|
||||||
from SMYMongoDBAPI import SMYMongoDBAPI
|
|
||||||
import os
|
|
||||||
# 根据环境动态选择MongoDB配置
|
|
||||||
if os.path.exists('/.dockerenv') or os.environ.get('PRODUCTION', '').lower() == 'true':
|
|
||||||
environment = "production"
|
|
||||||
else:
|
|
||||||
environment = "test"
|
|
||||||
mongo_api = SMYMongoDBAPI(environment)
|
|
||||||
if mongo_api.is_connected():
|
|
||||||
expired_count = mongo_api.clean_expired_verification_codes()
|
|
||||||
print(f"[验证码系统-MongoDB] 清理完成,删除了 {expired_count} 个过期验证码")
|
|
||||||
return expired_count
|
|
||||||
except Exception as e:
|
|
||||||
print(f"[验证码系统-MongoDB] MongoDB清理失败: {str(e)},尝试使用JSON文件")
|
|
||||||
|
|
||||||
# MongoDB失败,使用JSON文件备用
|
|
||||||
if not os.path.exists(VERIFICATION_CACHE_FILE):
|
|
||||||
return
|
|
||||||
|
|
||||||
try:
|
|
||||||
with open(VERIFICATION_CACHE_FILE, 'r', encoding='utf-8') as file:
|
|
||||||
verification_data = json.load(file)
|
|
||||||
|
|
||||||
current_time = time.time()
|
|
||||||
removed_keys = []
|
|
||||||
|
|
||||||
# 找出过期的验证码和已使用的验证码(超过1小时)
|
|
||||||
for qq_number, code_info in verification_data.items():
|
|
||||||
expire_at = code_info.get("expire_at", 0)
|
|
||||||
is_used = code_info.get("used", False)
|
|
||||||
used_at = code_info.get("used_at", 0)
|
|
||||||
|
|
||||||
should_remove = False
|
|
||||||
|
|
||||||
# 过期的验证码
|
|
||||||
if current_time > expire_at:
|
|
||||||
should_remove = True
|
|
||||||
print(f"[验证码清理-JSON] 移除过期验证码: QQ {qq_number}")
|
|
||||||
|
|
||||||
# 已使用超过1小时的验证码
|
|
||||||
elif is_used and used_at > 0 and (current_time - used_at) > 3600:
|
|
||||||
should_remove = True
|
|
||||||
print(f"[验证码清理-JSON] 移除已使用的验证码: QQ {qq_number}")
|
|
||||||
|
|
||||||
if should_remove:
|
|
||||||
removed_keys.append(qq_number)
|
|
||||||
|
|
||||||
# 移除标记的验证码
|
|
||||||
for key in removed_keys:
|
|
||||||
del verification_data[key]
|
|
||||||
|
|
||||||
# 保存更新后的数据
|
|
||||||
if removed_keys:
|
|
||||||
with open(VERIFICATION_CACHE_FILE, 'w', encoding='utf-8') as file:
|
|
||||||
json.dump(verification_data, file, indent=2, ensure_ascii=False)
|
|
||||||
print(f"[验证码清理-JSON] 共清理了 {len(removed_keys)} 个验证码")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"清理验证码失败: {str(e)}")
|
|
||||||
|
|
||||||
#获取验证码状态(优先使用MongoDB)
|
|
||||||
@staticmethod
|
|
||||||
def get_verification_status(qq_number):
|
|
||||||
"""
|
|
||||||
获取验证码状态(优先使用MongoDB)
|
|
||||||
|
|
||||||
参数:
|
|
||||||
qq_number (str): QQ号
|
|
||||||
|
|
||||||
返回:
|
|
||||||
dict: 验证码状态信息
|
|
||||||
"""
|
|
||||||
import time
|
|
||||||
|
|
||||||
# 优先尝试使用MongoDB
|
|
||||||
try:
|
|
||||||
from SMYMongoDBAPI import SMYMongoDBAPI
|
|
||||||
import os
|
|
||||||
# 根据环境动态选择MongoDB配置
|
|
||||||
if os.path.exists('/.dockerenv') or os.environ.get('PRODUCTION', '').lower() == 'true':
|
|
||||||
environment = "production"
|
|
||||||
else:
|
|
||||||
environment = "test"
|
|
||||||
mongo_api = SMYMongoDBAPI(environment)
|
|
||||||
if mongo_api.is_connected():
|
|
||||||
verification_codes = mongo_api.get_verification_codes()
|
|
||||||
if verification_codes and qq_number in verification_codes:
|
|
||||||
code_info = verification_codes[qq_number]
|
|
||||||
current_time = time.time()
|
|
||||||
|
|
||||||
return {
|
|
||||||
"status": "found",
|
|
||||||
"code": code_info.get("code", ""),
|
|
||||||
"code_type": code_info.get("code_type", "unknown"),
|
|
||||||
"used": code_info.get("used", False),
|
|
||||||
"expired": current_time > code_info.get("expire_at", 0),
|
|
||||||
"created_at": code_info.get("created_at", 0),
|
|
||||||
"expire_at": code_info.get("expire_at", 0),
|
|
||||||
"used_at": code_info.get("used_at", 0),
|
|
||||||
"source": "mongodb"
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
return {"status": "no_code", "source": "mongodb"}
|
|
||||||
except Exception as e:
|
|
||||||
print(f"[验证码系统-MongoDB] MongoDB状态查询失败: {str(e)},尝试使用JSON文件")
|
|
||||||
|
|
||||||
# MongoDB失败,使用JSON文件备用
|
|
||||||
if not os.path.exists(VERIFICATION_CACHE_FILE):
|
|
||||||
return {"status": "no_cache_file"}
|
|
||||||
|
|
||||||
try:
|
|
||||||
with open(VERIFICATION_CACHE_FILE, 'r', encoding='utf-8') as file:
|
|
||||||
verification_data = json.load(file)
|
|
||||||
|
|
||||||
if qq_number not in verification_data:
|
|
||||||
return {"status": "no_code"}
|
|
||||||
|
|
||||||
code_info = verification_data[qq_number]
|
|
||||||
current_time = time.time()
|
|
||||||
|
|
||||||
return {
|
|
||||||
"status": "found",
|
|
||||||
"code": code_info.get("code", ""),
|
|
||||||
"code_type": code_info.get("code_type", "unknown"),
|
|
||||||
"used": code_info.get("used", False),
|
|
||||||
"expired": current_time > code_info.get("expire_at", 0),
|
|
||||||
"created_at": code_info.get("created_at", 0),
|
|
||||||
"expire_at": code_info.get("expire_at", 0),
|
|
||||||
"used_at": code_info.get("used_at", 0),
|
|
||||||
"source": "json"
|
|
||||||
}
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
return {"status": "error", "message": str(e)}
|
|
||||||
|
|
||||||
|
|
||||||
# 测试邮件发送
|
|
||||||
if __name__ == "__main__":
|
|
||||||
# 清理过期验证码
|
|
||||||
EmailVerification.clean_expired_codes()
|
|
||||||
|
|
||||||
# 生成验证码
|
|
||||||
test_qq = input("请输入测试QQ号: ")
|
|
||||||
verification_code = EmailVerification.generate_verification_code()
|
|
||||||
print(f"生成的验证码: {verification_code}")
|
|
||||||
|
|
||||||
# 发送测试邮件
|
|
||||||
success, message = EmailVerification.send_verification_email(test_qq, verification_code)
|
|
||||||
print(f"发送结果: {success}, 消息: {message}")
|
|
||||||
|
|
||||||
if success:
|
|
||||||
# 保存验证码
|
|
||||||
EmailVerification.save_verification_code(test_qq, verification_code)
|
|
||||||
|
|
||||||
# 测试验证
|
|
||||||
test_input = input("请输入收到的验证码: ")
|
|
||||||
verify_success, verify_message = EmailVerification.verify_code(test_qq, test_input)
|
|
||||||
print(f"验证结果: {verify_success}, 消息: {verify_message}")
|
|
||||||
@@ -19,6 +19,8 @@ from modules.auth import auth_bp
|
|||||||
from modules.api_60s import api_60s_bp
|
from modules.api_60s import api_60s_bp
|
||||||
from modules.user_management import user_bp
|
from modules.user_management import user_bp
|
||||||
from modules.email_service import init_mail
|
from modules.email_service import init_mail
|
||||||
|
from modules.smallgame import smallgame_bp
|
||||||
|
from modules.aimodelapp import aimodelapp_bp
|
||||||
|
|
||||||
from config import Config
|
from config import Config
|
||||||
|
|
||||||
@@ -43,6 +45,8 @@ def create_app():
|
|||||||
app.register_blueprint(auth_bp, url_prefix='/api/auth')
|
app.register_blueprint(auth_bp, url_prefix='/api/auth')
|
||||||
app.register_blueprint(api_60s_bp, url_prefix='/api/60s')
|
app.register_blueprint(api_60s_bp, url_prefix='/api/60s')
|
||||||
app.register_blueprint(user_bp, url_prefix='/api/user')
|
app.register_blueprint(user_bp, url_prefix='/api/user')
|
||||||
|
app.register_blueprint(smallgame_bp, url_prefix='/api/smallgame')
|
||||||
|
app.register_blueprint(aimodelapp_bp, url_prefix='/api/aimodelapp')
|
||||||
|
|
||||||
# 基础路由
|
# 基础路由
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
@@ -55,7 +59,9 @@ def create_app():
|
|||||||
'endpoints': {
|
'endpoints': {
|
||||||
'auth': '/api/auth',
|
'auth': '/api/auth',
|
||||||
'60s_api': '/api/60s',
|
'60s_api': '/api/60s',
|
||||||
'user': '/api/user'
|
'user': '/api/user',
|
||||||
|
'smallgame': '/api/smallgame',
|
||||||
|
'aimodelapp': '/api/aimodelapp'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -102,6 +108,60 @@ def create_app():
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
return jsonify({'error': f'文件服务错误: {str(e)}'}), 500
|
return jsonify({'error': f'文件服务错误: {str(e)}'}), 500
|
||||||
|
|
||||||
|
# smallgame静态文件服务
|
||||||
|
@app.route('/smallgame/<path:filename>')
|
||||||
|
def serve_smallgame_files(filename):
|
||||||
|
"""提供smallgame目录下的静态文件服务"""
|
||||||
|
try:
|
||||||
|
# 获取项目根目录
|
||||||
|
project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
game_directory = os.path.join(project_root, 'frontend', 'smallgame')
|
||||||
|
|
||||||
|
# 安全检查:确保文件路径在允许的目录内
|
||||||
|
full_path = os.path.join(game_directory, filename)
|
||||||
|
if not os.path.commonpath([game_directory, full_path]) == game_directory:
|
||||||
|
return jsonify({'error': '非法文件路径'}), 403
|
||||||
|
|
||||||
|
# 检查文件是否存在
|
||||||
|
if not os.path.exists(full_path):
|
||||||
|
return jsonify({'error': '文件不存在'}), 404
|
||||||
|
|
||||||
|
# 获取文件目录和文件名
|
||||||
|
directory = os.path.dirname(full_path)
|
||||||
|
file_name = os.path.basename(full_path)
|
||||||
|
|
||||||
|
return send_from_directory(directory, file_name)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({'error': f'文件服务错误: {str(e)}'}), 500
|
||||||
|
|
||||||
|
# aimodelapp静态文件服务
|
||||||
|
@app.route('/aimodelapp/<path:filename>')
|
||||||
|
def serve_aimodelapp_files(filename):
|
||||||
|
"""提供aimodelapp目录下的静态文件服务"""
|
||||||
|
try:
|
||||||
|
# 获取项目根目录
|
||||||
|
project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
ai_directory = os.path.join(project_root, 'frontend', 'aimodelapp')
|
||||||
|
|
||||||
|
# 安全检查:确保文件路径在允许的目录内
|
||||||
|
full_path = os.path.join(ai_directory, filename)
|
||||||
|
if not os.path.commonpath([ai_directory, full_path]) == ai_directory:
|
||||||
|
return jsonify({'error': '非法文件路径'}), 403
|
||||||
|
|
||||||
|
# 检查文件是否存在
|
||||||
|
if not os.path.exists(full_path):
|
||||||
|
return jsonify({'error': '文件不存在'}), 404
|
||||||
|
|
||||||
|
# 获取文件目录和文件名
|
||||||
|
directory = os.path.dirname(full_path)
|
||||||
|
file_name = os.path.basename(full_path)
|
||||||
|
|
||||||
|
return send_from_directory(directory, file_name)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({'error': f'文件服务错误: {str(e)}'}), 500
|
||||||
|
|
||||||
# 错误处理
|
# 错误处理
|
||||||
@app.errorhandler(404)
|
@app.errorhandler(404)
|
||||||
def not_found(error):
|
def not_found(error):
|
||||||
|
|||||||
78
backend/modules/aimodelapp.py
Normal file
78
backend/modules/aimodelapp.py
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
AI应用模块 - 提供AI应用静态文件服务和目录扫描
|
||||||
|
Created by: 神奇万事通
|
||||||
|
Date: 2025-09-02
|
||||||
|
"""
|
||||||
|
|
||||||
|
from flask import Blueprint, jsonify
|
||||||
|
import os
|
||||||
|
|
||||||
|
aimodelapp_bp = Blueprint('aimodelapp', __name__)
|
||||||
|
|
||||||
|
@aimodelapp_bp.route('/scan-directories', methods=['GET'])
|
||||||
|
def scan_directories():
|
||||||
|
"""扫描aimodelapp目录结构"""
|
||||||
|
try:
|
||||||
|
# 获取项目根目录
|
||||||
|
project_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
ai_directory = os.path.join(project_root, 'frontend', 'aimodelapp')
|
||||||
|
|
||||||
|
if not os.path.exists(ai_directory):
|
||||||
|
return jsonify({
|
||||||
|
'success': False,
|
||||||
|
'message': 'aimodelapp目录不存在'
|
||||||
|
}), 404
|
||||||
|
|
||||||
|
apps = []
|
||||||
|
|
||||||
|
# 颜色渐变配置
|
||||||
|
gradient_colors = [
|
||||||
|
'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
||||||
|
'linear-gradient(135deg, #f093fb 0%, #f5576c 100%)',
|
||||||
|
'linear-gradient(135deg, #4facfe 0%, #00f2fe 100%)',
|
||||||
|
'linear-gradient(135deg, #43e97b 0%, #38f9d7 100%)',
|
||||||
|
'linear-gradient(135deg, #fa709a 0%, #fee140 100%)'
|
||||||
|
]
|
||||||
|
|
||||||
|
# 扫描目录
|
||||||
|
for i, app_name in enumerate(os.listdir(ai_directory)):
|
||||||
|
app_path = os.path.join(ai_directory, app_name)
|
||||||
|
index_path = os.path.join(app_path, 'index.html')
|
||||||
|
|
||||||
|
if os.path.isdir(app_path) and os.path.exists(index_path) and not app_name.endswith('.txt'):
|
||||||
|
# 读取HTML文件获取标题
|
||||||
|
try:
|
||||||
|
with open(index_path, 'r', encoding='utf-8') as f:
|
||||||
|
html_content = f.read()
|
||||||
|
title_match = html_content.find('<title>')
|
||||||
|
if title_match != -1:
|
||||||
|
title_end = html_content.find('</title>', title_match)
|
||||||
|
if title_end != -1:
|
||||||
|
title = html_content[title_match + 7:title_end].strip()
|
||||||
|
else:
|
||||||
|
title = app_name
|
||||||
|
else:
|
||||||
|
title = app_name
|
||||||
|
except:
|
||||||
|
title = app_name
|
||||||
|
|
||||||
|
apps.append({
|
||||||
|
'title': title,
|
||||||
|
'description': f'{app_name}AI应用',
|
||||||
|
'link': f'/aimodelapp/{app_name}/index.html',
|
||||||
|
'status': 'active',
|
||||||
|
'color': gradient_colors[i % len(gradient_colors)]
|
||||||
|
})
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'success': True,
|
||||||
|
'apps': apps
|
||||||
|
})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({
|
||||||
|
'success': False,
|
||||||
|
'message': f'扫描目录时出错: {str(e)}'
|
||||||
|
}), 500
|
||||||
@@ -6,334 +6,15 @@ Created by: 神奇万事通
|
|||||||
Date: 2025-09-02
|
Date: 2025-09-02
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from flask import Blueprint, jsonify, request
|
from flask import Blueprint, jsonify
|
||||||
import requests
|
import os
|
||||||
import json
|
|
||||||
from datetime import datetime, timedelta
|
|
||||||
import random
|
|
||||||
import time
|
|
||||||
|
|
||||||
api_60s_bp = Blueprint('api_60s', __name__)
|
api_60s_bp = Blueprint('api_60s', __name__)
|
||||||
|
|
||||||
# API配置
|
|
||||||
API_ENDPOINTS = {
|
|
||||||
'抖音热搜': {
|
|
||||||
'urls': [
|
|
||||||
'https://api.vvhan.com/api/hotlist?type=douyin',
|
|
||||||
'https://tenapi.cn/v2/douyinhot',
|
|
||||||
'https://api.oioweb.cn/api/common/tebie/dyhot'
|
|
||||||
],
|
|
||||||
'cache_time': 600 # 10分钟缓存
|
|
||||||
},
|
|
||||||
'微博热搜': {
|
|
||||||
'urls': [
|
|
||||||
'https://api.vvhan.com/api/hotlist?type=weibo',
|
|
||||||
'https://tenapi.cn/v2/wbhot',
|
|
||||||
'https://api.oioweb.cn/api/common/tebie/wbhot'
|
|
||||||
],
|
|
||||||
'cache_time': 300 # 5分钟缓存
|
|
||||||
},
|
|
||||||
'猫眼票房': {
|
|
||||||
'urls': [
|
|
||||||
'https://api.vvhan.com/api/hotlist?type=maoyan',
|
|
||||||
'https://tenapi.cn/v2/maoyan'
|
|
||||||
],
|
|
||||||
'cache_time': 3600 # 1小时缓存
|
|
||||||
},
|
|
||||||
'网易云音乐': {
|
|
||||||
'urls': [
|
|
||||||
'https://api.vvhan.com/api/hotlist?type=netease',
|
|
||||||
'https://tenapi.cn/v2/music'
|
|
||||||
],
|
|
||||||
'cache_time': 1800 # 30分钟缓存
|
|
||||||
},
|
|
||||||
'HackerNews': {
|
|
||||||
'urls': [
|
|
||||||
'https://api.vvhan.com/api/hotlist?type=hackernews',
|
|
||||||
'https://hacker-news.firebaseio.com/v0/topstories.json'
|
|
||||||
],
|
|
||||||
'cache_time': 1800 # 30分钟缓存
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# 内存缓存
|
|
||||||
cache = {}
|
|
||||||
|
|
||||||
def fetch_data_with_fallback(urls, timeout=10):
|
|
||||||
"""使用备用URL获取数据"""
|
|
||||||
for url in urls:
|
|
||||||
try:
|
|
||||||
response = requests.get(url, timeout=timeout, headers={
|
|
||||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
|
|
||||||
})
|
|
||||||
if response.status_code == 200:
|
|
||||||
return response.json()
|
|
||||||
except Exception as e:
|
|
||||||
print(f"URL {url} 失败: {str(e)}")
|
|
||||||
continue
|
|
||||||
return None
|
|
||||||
|
|
||||||
def get_cached_data(key, cache_time):
|
|
||||||
"""获取缓存数据"""
|
|
||||||
if key in cache:
|
|
||||||
cached_time, data = cache[key]
|
|
||||||
if datetime.now() - cached_time < timedelta(seconds=cache_time):
|
|
||||||
return data
|
|
||||||
return None
|
|
||||||
|
|
||||||
def set_cache_data(key, data):
|
|
||||||
"""设置缓存数据"""
|
|
||||||
cache[key] = (datetime.now(), data)
|
|
||||||
|
|
||||||
@api_60s_bp.route('/douyin', methods=['GET'])
|
|
||||||
def get_douyin_hot():
|
|
||||||
"""获取抖音热搜榜"""
|
|
||||||
try:
|
|
||||||
# 检查缓存
|
|
||||||
cached = get_cached_data('douyin', API_ENDPOINTS['抖音热搜']['cache_time'])
|
|
||||||
if cached:
|
|
||||||
return jsonify({
|
|
||||||
'success': True,
|
|
||||||
'data': cached,
|
|
||||||
'update_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
|
|
||||||
'from_cache': True
|
|
||||||
})
|
|
||||||
|
|
||||||
# 获取新数据
|
|
||||||
data = fetch_data_with_fallback(API_ENDPOINTS['抖音热搜']['urls'])
|
|
||||||
|
|
||||||
if data:
|
|
||||||
# 标准化数据格式
|
|
||||||
if 'data' in data:
|
|
||||||
hot_list = data['data']
|
|
||||||
elif isinstance(data, list):
|
|
||||||
hot_list = data
|
|
||||||
else:
|
|
||||||
hot_list = []
|
|
||||||
|
|
||||||
result = {
|
|
||||||
'title': '抖音热搜榜',
|
|
||||||
'subtitle': '实时热门话题 · 紧跟潮流趋势',
|
|
||||||
'update_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
|
|
||||||
'total': len(hot_list),
|
|
||||||
'list': hot_list[:50] # 最多返回50条
|
|
||||||
}
|
|
||||||
|
|
||||||
# 设置缓存
|
|
||||||
set_cache_data('douyin', result)
|
|
||||||
|
|
||||||
return jsonify({
|
|
||||||
'success': True,
|
|
||||||
'data': result,
|
|
||||||
'from_cache': False
|
|
||||||
})
|
|
||||||
else:
|
|
||||||
return jsonify({
|
|
||||||
'success': False,
|
|
||||||
'message': '获取数据失败,所有数据源暂时不可用'
|
|
||||||
}), 503
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
return jsonify({
|
|
||||||
'success': False,
|
|
||||||
'message': f'服务器错误: {str(e)}'
|
|
||||||
}), 500
|
|
||||||
|
|
||||||
@api_60s_bp.route('/weibo', methods=['GET'])
|
|
||||||
def get_weibo_hot():
|
|
||||||
"""获取微博热搜榜"""
|
|
||||||
try:
|
|
||||||
# 检查缓存
|
|
||||||
cached = get_cached_data('weibo', API_ENDPOINTS['微博热搜']['cache_time'])
|
|
||||||
if cached:
|
|
||||||
return jsonify({
|
|
||||||
'success': True,
|
|
||||||
'data': cached,
|
|
||||||
'update_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
|
|
||||||
'from_cache': True
|
|
||||||
})
|
|
||||||
|
|
||||||
# 获取新数据
|
|
||||||
data = fetch_data_with_fallback(API_ENDPOINTS['微博热搜']['urls'])
|
|
||||||
|
|
||||||
if data:
|
|
||||||
if 'data' in data:
|
|
||||||
hot_list = data['data']
|
|
||||||
elif isinstance(data, list):
|
|
||||||
hot_list = data
|
|
||||||
else:
|
|
||||||
hot_list = []
|
|
||||||
|
|
||||||
result = {
|
|
||||||
'title': '微博热搜榜',
|
|
||||||
'subtitle': '热门话题 · 实时更新',
|
|
||||||
'update_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
|
|
||||||
'total': len(hot_list),
|
|
||||||
'list': hot_list[:50]
|
|
||||||
}
|
|
||||||
|
|
||||||
set_cache_data('weibo', result)
|
|
||||||
|
|
||||||
return jsonify({
|
|
||||||
'success': True,
|
|
||||||
'data': result,
|
|
||||||
'from_cache': False
|
|
||||||
})
|
|
||||||
else:
|
|
||||||
return jsonify({
|
|
||||||
'success': False,
|
|
||||||
'message': '获取数据失败,所有数据源暂时不可用'
|
|
||||||
}), 503
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
return jsonify({
|
|
||||||
'success': False,
|
|
||||||
'message': f'服务器错误: {str(e)}'
|
|
||||||
}), 500
|
|
||||||
|
|
||||||
@api_60s_bp.route('/maoyan', methods=['GET'])
|
|
||||||
def get_maoyan_box_office():
|
|
||||||
"""获取猫眼票房排行榜"""
|
|
||||||
try:
|
|
||||||
cached = get_cached_data('maoyan', API_ENDPOINTS['猫眼票房']['cache_time'])
|
|
||||||
if cached:
|
|
||||||
return jsonify({
|
|
||||||
'success': True,
|
|
||||||
'data': cached,
|
|
||||||
'from_cache': True
|
|
||||||
})
|
|
||||||
|
|
||||||
data = fetch_data_with_fallback(API_ENDPOINTS['猫眼票房']['urls'])
|
|
||||||
|
|
||||||
if data:
|
|
||||||
if 'data' in data:
|
|
||||||
box_office_list = data['data']
|
|
||||||
elif isinstance(data, list):
|
|
||||||
box_office_list = data
|
|
||||||
else:
|
|
||||||
box_office_list = []
|
|
||||||
|
|
||||||
result = {
|
|
||||||
'title': '猫眼票房排行榜',
|
|
||||||
'subtitle': '实时票房数据',
|
|
||||||
'update_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
|
|
||||||
'total': len(box_office_list),
|
|
||||||
'list': box_office_list[:20]
|
|
||||||
}
|
|
||||||
|
|
||||||
set_cache_data('maoyan', result)
|
|
||||||
|
|
||||||
return jsonify({
|
|
||||||
'success': True,
|
|
||||||
'data': result,
|
|
||||||
'from_cache': False
|
|
||||||
})
|
|
||||||
else:
|
|
||||||
return jsonify({
|
|
||||||
'success': False,
|
|
||||||
'message': '获取数据失败'
|
|
||||||
}), 503
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
return jsonify({
|
|
||||||
'success': False,
|
|
||||||
'message': f'服务器错误: {str(e)}'
|
|
||||||
}), 500
|
|
||||||
|
|
||||||
@api_60s_bp.route('/60s', methods=['GET'])
|
|
||||||
def get_60s_news():
|
|
||||||
"""获取每天60秒读懂世界"""
|
|
||||||
try:
|
|
||||||
urls = [
|
|
||||||
'https://60s-cf.viki.moe',
|
|
||||||
'https://60s.viki.moe',
|
|
||||||
'https://60s.b23.run'
|
|
||||||
]
|
|
||||||
|
|
||||||
data = fetch_data_with_fallback(urls)
|
|
||||||
|
|
||||||
if data:
|
|
||||||
return jsonify({
|
|
||||||
'success': True,
|
|
||||||
'data': {
|
|
||||||
'title': '每天60秒读懂世界',
|
|
||||||
'content': data,
|
|
||||||
'update_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
else:
|
|
||||||
return jsonify({
|
|
||||||
'success': False,
|
|
||||||
'message': '获取数据失败'
|
|
||||||
}), 503
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
return jsonify({
|
|
||||||
'success': False,
|
|
||||||
'message': f'服务器错误: {str(e)}'
|
|
||||||
}), 500
|
|
||||||
|
|
||||||
@api_60s_bp.route('/bing-wallpaper', methods=['GET'])
|
|
||||||
def get_bing_wallpaper():
|
|
||||||
"""获取必应每日壁纸"""
|
|
||||||
try:
|
|
||||||
url = 'https://api.vvhan.com/api/bing'
|
|
||||||
response = requests.get(url, timeout=10)
|
|
||||||
|
|
||||||
if response.status_code == 200:
|
|
||||||
return jsonify({
|
|
||||||
'success': True,
|
|
||||||
'data': {
|
|
||||||
'title': '必应每日壁纸',
|
|
||||||
'image_url': response.url,
|
|
||||||
'update_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
else:
|
|
||||||
return jsonify({
|
|
||||||
'success': False,
|
|
||||||
'message': '获取壁纸失败'
|
|
||||||
}), 503
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
return jsonify({
|
|
||||||
'success': False,
|
|
||||||
'message': f'服务器错误: {str(e)}'
|
|
||||||
}), 500
|
|
||||||
|
|
||||||
@api_60s_bp.route('/weather', methods=['GET'])
|
|
||||||
def get_weather():
|
|
||||||
"""获取天气信息"""
|
|
||||||
try:
|
|
||||||
city = request.args.get('city', '北京')
|
|
||||||
url = f'https://api.vvhan.com/api/weather?city={city}'
|
|
||||||
|
|
||||||
response = requests.get(url, timeout=10)
|
|
||||||
|
|
||||||
if response.status_code == 200:
|
|
||||||
data = response.json()
|
|
||||||
return jsonify({
|
|
||||||
'success': True,
|
|
||||||
'data': data
|
|
||||||
})
|
|
||||||
else:
|
|
||||||
return jsonify({
|
|
||||||
'success': False,
|
|
||||||
'message': '获取天气信息失败'
|
|
||||||
}), 503
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
return jsonify({
|
|
||||||
'success': False,
|
|
||||||
'message': f'服务器错误: {str(e)}'
|
|
||||||
}), 500
|
|
||||||
|
|
||||||
@api_60s_bp.route('/scan-directories', methods=['GET'])
|
@api_60s_bp.route('/scan-directories', methods=['GET'])
|
||||||
def scan_directories():
|
def scan_directories():
|
||||||
"""扫描60sapi目录结构"""
|
"""扫描60sapi目录结构"""
|
||||||
try:
|
try:
|
||||||
import os
|
|
||||||
|
|
||||||
# 获取项目根目录
|
# 获取项目根目录
|
||||||
project_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
project_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
api_directory = os.path.join(project_root, 'frontend', '60sapi')
|
api_directory = os.path.join(project_root, 'frontend', '60sapi')
|
||||||
@@ -395,7 +76,7 @@ def scan_directories():
|
|||||||
apis.append({
|
apis.append({
|
||||||
'title': title,
|
'title': title,
|
||||||
'description': f'{module_name}相关功能',
|
'description': f'{module_name}相关功能',
|
||||||
'link': f'/60sapi/{category_name}/{module_name}/index.html',
|
'link': f'http://localhost:5000/60sapi/{category_name}/{module_name}/index.html',
|
||||||
'status': 'active',
|
'status': 'active',
|
||||||
'color': gradient_colors[i % len(gradient_colors)]
|
'color': gradient_colors[i % len(gradient_colors)]
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -317,6 +317,7 @@ def login():
|
|||||||
session['user_id'] = str(user['_id'])
|
session['user_id'] = str(user['_id'])
|
||||||
session['email'] = email
|
session['email'] = email
|
||||||
session['username'] = user.get('用户名', '')
|
session['username'] = user.get('用户名', '')
|
||||||
|
session['logged_in'] = True
|
||||||
session.permanent = True
|
session.permanent = True
|
||||||
|
|
||||||
return jsonify({
|
return jsonify({
|
||||||
@@ -394,13 +395,14 @@ def logout():
|
|||||||
def check_login():
|
def check_login():
|
||||||
"""检查登录状态"""
|
"""检查登录状态"""
|
||||||
try:
|
try:
|
||||||
if session.get('logged_in'):
|
if session.get('logged_in') and session.get('user_id'):
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'success': True,
|
'success': True,
|
||||||
'logged_in': True,
|
'logged_in': True,
|
||||||
'user': {
|
'user': {
|
||||||
'account': session.get('account'),
|
'id': session.get('user_id'),
|
||||||
'user_id': session.get('user_id')
|
'email': session.get('email'),
|
||||||
|
'username': session.get('username')
|
||||||
}
|
}
|
||||||
}), 200
|
}), 200
|
||||||
else:
|
else:
|
||||||
|
|||||||
78
backend/modules/smallgame.py
Normal file
78
backend/modules/smallgame.py
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
小游戏模块 - 提供小游戏静态文件服务和目录扫描
|
||||||
|
Created by: 神奇万事通
|
||||||
|
Date: 2025-09-02
|
||||||
|
"""
|
||||||
|
|
||||||
|
from flask import Blueprint, jsonify
|
||||||
|
import os
|
||||||
|
|
||||||
|
smallgame_bp = Blueprint('smallgame', __name__)
|
||||||
|
|
||||||
|
@smallgame_bp.route('/scan-directories', methods=['GET'])
|
||||||
|
def scan_directories():
|
||||||
|
"""扫描smallgame目录结构"""
|
||||||
|
try:
|
||||||
|
# 获取项目根目录
|
||||||
|
project_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
game_directory = os.path.join(project_root, 'frontend', 'smallgame')
|
||||||
|
|
||||||
|
if not os.path.exists(game_directory):
|
||||||
|
return jsonify({
|
||||||
|
'success': False,
|
||||||
|
'message': 'smallgame目录不存在'
|
||||||
|
}), 404
|
||||||
|
|
||||||
|
games = []
|
||||||
|
|
||||||
|
# 颜色渐变配置
|
||||||
|
gradient_colors = [
|
||||||
|
'linear-gradient(135deg, #ff9a9e 0%, #fecfef 100%)',
|
||||||
|
'linear-gradient(135deg, #a8edea 0%, #fed6e3 100%)',
|
||||||
|
'linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%)',
|
||||||
|
'linear-gradient(135deg, #ff8a80 0%, #ffab91 100%)',
|
||||||
|
'linear-gradient(135deg, #81c784 0%, #aed581 100%)'
|
||||||
|
]
|
||||||
|
|
||||||
|
# 扫描目录
|
||||||
|
for i, game_name in enumerate(os.listdir(game_directory)):
|
||||||
|
game_path = os.path.join(game_directory, game_name)
|
||||||
|
index_path = os.path.join(game_path, 'index.html')
|
||||||
|
|
||||||
|
if os.path.isdir(game_path) and os.path.exists(index_path) and not game_name.endswith('.txt'):
|
||||||
|
# 读取HTML文件获取标题
|
||||||
|
try:
|
||||||
|
with open(index_path, 'r', encoding='utf-8') as f:
|
||||||
|
html_content = f.read()
|
||||||
|
title_match = html_content.find('<title>')
|
||||||
|
if title_match != -1:
|
||||||
|
title_end = html_content.find('</title>', title_match)
|
||||||
|
if title_end != -1:
|
||||||
|
title = html_content[title_match + 7:title_end].strip()
|
||||||
|
else:
|
||||||
|
title = game_name
|
||||||
|
else:
|
||||||
|
title = game_name
|
||||||
|
except:
|
||||||
|
title = game_name
|
||||||
|
|
||||||
|
games.append({
|
||||||
|
'title': title,
|
||||||
|
'description': f'{game_name}小游戏',
|
||||||
|
'link': f'/smallgame/{game_name}/index.html',
|
||||||
|
'status': 'active',
|
||||||
|
'color': gradient_colors[i % len(gradient_colors)]
|
||||||
|
})
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'success': True,
|
||||||
|
'games': games
|
||||||
|
})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({
|
||||||
|
'success': False,
|
||||||
|
'message': f'扫描目录时出错: {str(e)}'
|
||||||
|
}), 500
|
||||||
@@ -17,8 +17,8 @@ class KFCGenerator {
|
|||||||
// 加载API接口列表
|
// 加载API接口列表
|
||||||
async loadApiEndpoints() {
|
async loadApiEndpoints() {
|
||||||
try {
|
try {
|
||||||
const response = await fetch('./接口集合.json');
|
// 直接硬编码API端点,避免CORS问题
|
||||||
this.apiEndpoints = await response.json();
|
this.apiEndpoints = ["https://60s.api.shumengya.top"];
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('加载API接口列表失败:', error);
|
console.error('加载API接口列表失败:', error);
|
||||||
this.showToast('加载接口配置失败', 'error');
|
this.showToast('加载接口配置失败', 'error');
|
||||||
|
|||||||
@@ -1 +1,8 @@
|
|||||||
{"code":200,"message":"获取成功。数据来自官方/权威源头,以确保稳定与实时。开源地址 https://github.com/vikiboss/60s,反馈群 595941841","data":{"index":369,"kfc":"我不想你带着情绪过夜,这样会把你推的越来越远,我不想你离开我,我希望你可以多爱我一点,我不想因为一点点小问题就让我们两之间的感情产生隔阂。对我来说,双向奔赴的感情相处才有意义,看不到希望的事情我没办法坚持太久,我不想浪费时间在一些无意义无结果的事情上,时间应该用来做我该做的事情。很多人走不到最后,是因为理解不同步,但我跟你在一起,我知道你什样的人,我了解你甚至比了解自己还多,我和你在一起,不是因为其他,而是因为今天肯德基疯狂星期四,我希望你能请我吃!"}}
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "获取成功。数据来自官方/权威源头,以确保稳定与实时。开源地址 https://github.com/vikiboss/60s,反馈群 595941841",
|
||||||
|
"data": {
|
||||||
|
"index": 78,
|
||||||
|
"kfc": "我叫夯大力 立冬给我准备了糖炒栗子了没有 没准备的自动绝交 再 v 我 50 吃疯狂星期四 然后再给我点杯奶茶 再给我两万块钱 懂吗你们"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,11 +4,7 @@ class HitokotoApp {
|
|||||||
constructor() {
|
constructor() {
|
||||||
// API接口列表
|
// API接口列表
|
||||||
this.apiEndpoints = [
|
this.apiEndpoints = [
|
||||||
"https://60s-cf.viki.moe",
|
"https://60s.api.shumengya.top"
|
||||||
"https://60s.viki.moe",
|
|
||||||
"https://60s.b23.run",
|
|
||||||
"https://60s.114128.xyz",
|
|
||||||
"https://60s-cf.114128.xyz"
|
|
||||||
];
|
];
|
||||||
|
|
||||||
this.currentEndpointIndex = 0;
|
this.currentEndpointIndex = 0;
|
||||||
@@ -117,7 +113,8 @@ class HitokotoApp {
|
|||||||
const timeoutId = setTimeout(() => controller.abort(), 10000); // 10秒超时
|
const timeoutId = setTimeout(() => controller.abort(), 10000); // 10秒超时
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${endpoint}/v2/hitokoto?encoding=text`, {
|
// 移除URL中的encoding=text参数,确保返回JSON格式
|
||||||
|
const response = await fetch(`${endpoint}/v2/hitokoto`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
'Accept': 'application/json',
|
'Accept': 'application/json',
|
||||||
|
|||||||
16
frontend/60sapi/娱乐消遣/随机冷笑话/css/Untitled-1.html
Normal file
16
frontend/60sapi/娱乐消遣/随机冷笑话/css/Untitled-1.html
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>每日笑话</title>
|
||||||
|
<link rel="stylesheet" href="style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="card">
|
||||||
|
<p id="joke">加载中...</p>
|
||||||
|
<button id="next">换一个</button>
|
||||||
|
</div>
|
||||||
|
<script src="script.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
107
frontend/60sapi/娱乐消遣/随机冷笑话/css/background.css
Normal file
107
frontend/60sapi/娱乐消遣/随机冷笑话/css/background.css
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
/* background.css - 动态渐变背景 */
|
||||||
|
body {
|
||||||
|
background: linear-gradient(-45deg, #ee7752, #e73c7e, #23a6d5, #23d5ab);
|
||||||
|
background-size: 400% 400%;
|
||||||
|
animation: gradient 15s ease infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes gradient {
|
||||||
|
0% {
|
||||||
|
background-position: 0% 50%;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
background-position: 100% 50%;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
background-position: 0% 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
:root {
|
||||||
|
--bg-yellow: #FFFDE7; /* 浅黄 */
|
||||||
|
--bg-blue: #E3F2FD; /* 淡蓝 */
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background: linear-gradient(180deg, var(--bg-yellow) 0%, var(--bg-blue) 100%);
|
||||||
|
background-attachment: fixed; /* 固定背景,滚动时不移动 */
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: background-color 0.5s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Light Theme (Default) */
|
||||||
|
[data-theme="light"] {
|
||||||
|
background: linear-gradient(to bottom, #87CEEB, #B0E0E6);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dark Theme */
|
||||||
|
[data-theme="dark"] {
|
||||||
|
background: linear-gradient(to bottom, #232526, #414345);
|
||||||
|
}
|
||||||
|
[data-theme="dark"] .snowflake {
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Winter Theme */
|
||||||
|
[data-theme="winter"] {
|
||||||
|
background: linear-gradient(to bottom, #a1c4fd, #c2e9fb);
|
||||||
|
}
|
||||||
|
[data-theme="winter"] .background-bottom {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100px;
|
||||||
|
background: linear-gradient(to top, white, rgba(255, 255, 255, 0));
|
||||||
|
z-index: -1;
|
||||||
|
border-radius: 50% 50% 0 0 / 20px;
|
||||||
|
box-shadow: 0 -10px 20px rgba(255, 255, 255, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#snowflake-container {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.snowflake {
|
||||||
|
position: absolute;
|
||||||
|
top: -10%;
|
||||||
|
color: white;
|
||||||
|
font-size: 20px;
|
||||||
|
user-select: none;
|
||||||
|
animation: fall linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fall {
|
||||||
|
to {
|
||||||
|
transform: translateY(105vh) rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#frost-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: url('https://www.transparenttextures.com/patterns/ice-age.png') repeat;
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
transition: opacity 0.5s ease-in-out;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
#frost-overlay.is-frosted {
|
||||||
|
opacity: 0.3;
|
||||||
|
}
|
||||||
217
frontend/60sapi/娱乐消遣/随机冷笑话/css/style.css
Normal file
217
frontend/60sapi/娱乐消遣/随机冷笑话/css/style.css
Normal file
@@ -0,0 +1,217 @@
|
|||||||
|
:root {
|
||||||
|
--primary-color-light: #4A90E2;
|
||||||
|
--text-color-light: #333;
|
||||||
|
--card-bg-light: rgba(255, 255, 255, 0.85);
|
||||||
|
|
||||||
|
--primary-color-dark: #5271C4;
|
||||||
|
--text-color-dark: #E0E0E0;
|
||||||
|
--card-bg-dark: rgba(40, 40, 40, 0.85);
|
||||||
|
|
||||||
|
--primary-color-winter: #6A82FB;
|
||||||
|
--text-color-winter: #2c3e50;
|
||||||
|
--card-bg-winter: rgba(255, 255, 255, 0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 100vh;
|
||||||
|
padding: 20px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
text-align: center;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-nav {
|
||||||
|
position: absolute;
|
||||||
|
top: 20px;
|
||||||
|
right: 20px;
|
||||||
|
background: rgba(255, 255, 255, 0.3);
|
||||||
|
padding: 5px;
|
||||||
|
border-radius: 50px;
|
||||||
|
backdrop-filter: blur(5px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-switcher {
|
||||||
|
display: flex;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-btn {
|
||||||
|
background: transparent;
|
||||||
|
border: 2px solid transparent;
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
font-size: 1.5em;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: transform 0.2s, border-color 0.2s;
|
||||||
|
}
|
||||||
|
.theme-btn:hover {
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
.theme-btn.active {
|
||||||
|
border-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-family: 'ZCOOL KuaiLe', cursive;
|
||||||
|
font-size: 3em;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
transition: color 0.5s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.joke-stream {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 500px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.joke-card {
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 30px 40px;
|
||||||
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15);
|
||||||
|
width: 100%;
|
||||||
|
max-width: 500px;
|
||||||
|
min-height: 150px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
position: relative;
|
||||||
|
backdrop-filter: blur(8px);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
|
transition: background-color 0.5s ease, border-color 0.5s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
#joke-text {
|
||||||
|
font-size: 1.5em;
|
||||||
|
line-height: 1.6;
|
||||||
|
transition: opacity 0.3s, color 0.5s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Theming --- */
|
||||||
|
|
||||||
|
/* Light Theme */
|
||||||
|
[data-theme="light"] .title { color: white; text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.2); }
|
||||||
|
[data-theme="light"] .joke-card { background-color: var(--card-bg-light); }
|
||||||
|
[data-theme="light"] #joke-text { color: var(--text-color-light); }
|
||||||
|
[data-theme="light"] #new-joke-btn { background-color: var(--primary-color-light); box-shadow: 0 4px 15px rgba(74, 144, 226, 0.4); }
|
||||||
|
[data-theme="light"] footer { color: rgba(255, 255, 255, 0.8); }
|
||||||
|
|
||||||
|
/* Dark Theme */
|
||||||
|
[data-theme="dark"] .title { color: #EAEAEA; text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5); }
|
||||||
|
[data-theme="dark"] .joke-card { background-color: var(--card-bg-dark); border-color: rgba(255, 255, 255, 0.1); }
|
||||||
|
[data-theme="dark"] #joke-text { color: var(--text-color-dark); }
|
||||||
|
[data-theme="dark"] #new-joke-btn { background-color: var(--primary-color-dark); box-shadow: 0 4px 15px rgba(82, 113, 196, 0.4); }
|
||||||
|
[data-theme="dark"] footer { color: rgba(200, 200, 200, 0.7); }
|
||||||
|
|
||||||
|
/* Winter Theme */
|
||||||
|
[data-theme="winter"] .title { color: #1e3a5f; text-shadow: 1px 1px 2px rgba(255, 255, 255, 0.7); }
|
||||||
|
[data-theme="winter"] .joke-card {
|
||||||
|
background-color: var(--card-bg-winter);
|
||||||
|
border-color: rgba(255, 255, 255, 0.8);
|
||||||
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1), inset 0 0 15px rgba(255, 255, 255, 0.5);
|
||||||
|
}
|
||||||
|
[data-theme="winter"] #joke-text { color: var(--text-color-winter); }
|
||||||
|
[data-theme="winter"] #new-joke-btn { background-color: var(--primary-color-winter); box-shadow: 0 4px 15px rgba(106, 130, 251, 0.4); }
|
||||||
|
[data-theme="winter"] footer { color: #1e3a5f; }
|
||||||
|
|
||||||
|
|
||||||
|
.controls {
|
||||||
|
margin-top: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#new-joke-btn {
|
||||||
|
color: white;
|
||||||
|
font-size: 1.2em;
|
||||||
|
font-weight: bold;
|
||||||
|
padding: 15px 35px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 50px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: transform 0.2s ease, box-shadow 0.2s ease, background-color 0.5s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
#new-joke-btn:hover {
|
||||||
|
transform: translateY(-3px);
|
||||||
|
}
|
||||||
|
|
||||||
|
#new-joke-btn:active {
|
||||||
|
transform: translateY(1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.interactions {
|
||||||
|
margin-top: 25px;
|
||||||
|
display: flex;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.interaction-btn {
|
||||||
|
background: rgba(255, 255, 255, 0.7);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.9);
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
font-size: 1.5em;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: transform 0.2s, background-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.interaction-btn:hover {
|
||||||
|
transform: scale(1.1);
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 10px;
|
||||||
|
font-size: 0.9em;
|
||||||
|
transition: color 0.5s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Loader */
|
||||||
|
#loader {
|
||||||
|
position: absolute;
|
||||||
|
transition: color 0.5s ease;
|
||||||
|
}
|
||||||
|
[data-theme="light"] #loader { color: var(--primary-color-light); }
|
||||||
|
[data-theme="dark"] #loader { color: var(--primary-color-dark); }
|
||||||
|
[data-theme="winter"] #loader { color: var(--primary-color-winter); }
|
||||||
|
|
||||||
|
.snowflake-loader {
|
||||||
|
font-size: 40px;
|
||||||
|
display: inline-block;
|
||||||
|
animation: spin 1.5s linear infinite;
|
||||||
|
}
|
||||||
|
.snowflake-loader::before {
|
||||||
|
content: '❄';
|
||||||
|
}
|
||||||
|
@keyframes spin {
|
||||||
|
to { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive */
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.title {
|
||||||
|
font-size: 2.5em;
|
||||||
|
}
|
||||||
|
.joke-card {
|
||||||
|
padding: 25px;
|
||||||
|
}
|
||||||
|
#joke-text {
|
||||||
|
font-size: 1.2em;
|
||||||
|
}
|
||||||
|
.top-nav {
|
||||||
|
top: 10px;
|
||||||
|
right: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
58
frontend/60sapi/娱乐消遣/随机冷笑话/index.html
Normal file
58
frontend/60sapi/娱乐消遣/随机冷笑话/index.html
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>随机冷笑话</title>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=ZCOOL+KuaiLe&display=swap" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="css/background.css">
|
||||||
|
<link rel="stylesheet" href="css/style.css">
|
||||||
|
</head>
|
||||||
|
<body data-theme="light">
|
||||||
|
<div id="snowflake-container"></div>
|
||||||
|
<div id="frost-overlay"></div>
|
||||||
|
<div class="background-bottom"></div>
|
||||||
|
|
||||||
|
<nav class="top-nav">
|
||||||
|
<div class="theme-switcher">
|
||||||
|
<button class="theme-btn" data-theme-target="light" title="清新风">☀️</button>
|
||||||
|
<button class="theme-btn" data-theme-target="dark" title="暗黑风">🌙</button>
|
||||||
|
<button class="theme-btn" data-theme-target="winter" title="冰雪风">❄️</button>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<header>
|
||||||
|
<h1 class="title">冷笑话生成器</h1>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main class="joke-card">
|
||||||
|
<div id="loader" class="hidden">
|
||||||
|
<div class="snowflake-loader"></div>
|
||||||
|
<p>思考中...</p>
|
||||||
|
</div>
|
||||||
|
<p id="joke-text">点击下面的按钮,来点冷笑话吧!</p>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<div class="controls">
|
||||||
|
<button id="new-joke-btn">再来一个</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="interactions">
|
||||||
|
<button class="interaction-btn" id="like-btn" title="好笑">👍</button>
|
||||||
|
<button class="interaction-btn" id="dislike-btn" title="不好笑">👎</button>
|
||||||
|
<button class="interaction-btn" id="collect-btn" title="收藏">⭐️</button>
|
||||||
|
<button class="interaction-btn" id="share-btn" title="分享">🔗</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<p>© 2024 冷笑话工坊</p>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<audio id="wind-sound" src="https://www.soundjay.com/nature/sounds/wind-howl-01.mp3" preload="auto"></audio>
|
||||||
|
<audio id="snow-sound" src="https://www.soundjay.com/nature/sounds/walking-in-snow-01.mp3" preload="auto"></audio>
|
||||||
|
|
||||||
|
<script src="js/script.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
117
frontend/60sapi/娱乐消遣/随机冷笑话/js/script.js
Normal file
117
frontend/60sapi/娱乐消遣/随机冷笑话/js/script.js
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
const jokeTextElem = document.getElementById('joke-text');
|
||||||
|
const newJokeBtn = document.getElementById('new-joke-btn');
|
||||||
|
const snowflakeContainer = document.getElementById('snowflake-container');
|
||||||
|
const frostOverlay = document.getElementById('frost-overlay');
|
||||||
|
const windSound = document.getElementById('wind-sound');
|
||||||
|
const snowSound = document.getElementById('snow-sound');
|
||||||
|
const loader = document.getElementById('loader');
|
||||||
|
const themeBtns = document.querySelectorAll('.theme-btn');
|
||||||
|
|
||||||
|
const apiEndpoints = [
|
||||||
|
'https://60s.api.shumengya.top/v2/dad-joke',
|
||||||
|
];
|
||||||
|
let currentApiIndex = 0;
|
||||||
|
|
||||||
|
async function fetchJoke() {
|
||||||
|
jokeTextElem.classList.add('hidden');
|
||||||
|
loader.classList.remove('hidden');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(apiEndpoints[currentApiIndex]);
|
||||||
|
if (!response.ok) throw new Error('Network response was not ok');
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
if (data.code === 200 && data.data.content) {
|
||||||
|
updateJokeText(data.data.content);
|
||||||
|
if (document.body.dataset.theme === 'winter' && Math.random() < 0.3) {
|
||||||
|
triggerFrostEffect();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error('API returned invalid data');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Fetch error:', error);
|
||||||
|
currentApiIndex = (currentApiIndex + 1) % apiEndpoints.length;
|
||||||
|
if (currentApiIndex !== 0) {
|
||||||
|
fetchJoke();
|
||||||
|
} else {
|
||||||
|
jokeTextElem.textContent = '冰箱坏了,暂时没有冷笑话...';
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
loader.classList.add('hidden');
|
||||||
|
jokeTextElem.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateJokeText(text) {
|
||||||
|
jokeTextElem.textContent = '';
|
||||||
|
let i = 0;
|
||||||
|
const typing = setInterval(() => {
|
||||||
|
if (i < text.length) {
|
||||||
|
jokeTextElem.textContent += text.charAt(i);
|
||||||
|
i++;
|
||||||
|
} else {
|
||||||
|
clearInterval(typing);
|
||||||
|
}
|
||||||
|
}, 50);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createSnowflakes() {
|
||||||
|
const snowflakeCount = document.body.dataset.theme === 'dark' ? 50 : 30;
|
||||||
|
snowflakeContainer.innerHTML = '';
|
||||||
|
for (let i = 0; i < snowflakeCount; i++) {
|
||||||
|
const snowflake = document.createElement('div');
|
||||||
|
snowflake.className = 'snowflake';
|
||||||
|
snowflake.textContent = '❄️';
|
||||||
|
|
||||||
|
snowflake.style.left = `${Math.random() * 100}vw`;
|
||||||
|
snowflake.style.fontSize = `${Math.random() * 15 + 10}px`;
|
||||||
|
snowflake.style.opacity = Math.random() * 0.5 + 0.3;
|
||||||
|
|
||||||
|
const duration = Math.random() * 10 + 8;
|
||||||
|
const delay = Math.random() * 10;
|
||||||
|
|
||||||
|
snowflake.style.animation = `fall ${duration}s linear ${delay}s infinite`;
|
||||||
|
|
||||||
|
snowflakeContainer.appendChild(snowflake);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function triggerFrostEffect() {
|
||||||
|
frostOverlay.classList.add('is-frosted');
|
||||||
|
windSound.play().catch(e => console.error("Audio play failed:", e));
|
||||||
|
setTimeout(() => {
|
||||||
|
frostOverlay.classList.remove('is-frosted');
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setTheme(theme) {
|
||||||
|
document.body.dataset.theme = theme;
|
||||||
|
localStorage.setItem('joke-theme', theme);
|
||||||
|
|
||||||
|
themeBtns.forEach(btn => {
|
||||||
|
btn.classList.toggle('active', btn.dataset.themeTarget === theme);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (theme === 'winter') {
|
||||||
|
snowSound.play().catch(e => console.error("Audio play failed:", e));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recreate snowflakes for theme-specific density
|
||||||
|
createSnowflakes();
|
||||||
|
}
|
||||||
|
|
||||||
|
themeBtns.forEach(btn => {
|
||||||
|
btn.addEventListener('click', () => {
|
||||||
|
setTheme(btn.dataset.themeTarget);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
newJokeBtn.addEventListener('click', fetchJoke);
|
||||||
|
|
||||||
|
// Initial setup
|
||||||
|
const savedTheme = localStorage.getItem('joke-theme') || 'light';
|
||||||
|
setTheme(savedTheme);
|
||||||
|
fetchJoke();
|
||||||
|
});
|
||||||
47
frontend/60sapi/娱乐消遣/随机冷笑话/接口集合.json
Normal file
47
frontend/60sapi/娱乐消遣/随机冷笑话/接口集合.json
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
{
|
||||||
|
"api_name": "60s-api",
|
||||||
|
"api_version": "2.22.1",
|
||||||
|
"api_docs": "https://docs.60s-api.viki.moe",
|
||||||
|
"author": "Viki <hi@viki.moe>",
|
||||||
|
"user_group": "595941841",
|
||||||
|
"github_repo": "https://github.com/vikiboss/60s",
|
||||||
|
"updated": "2025/09/01 11:12:08",
|
||||||
|
"updated_at": 1756696328000,
|
||||||
|
"endpoints": [
|
||||||
|
"/v2/60s",
|
||||||
|
"/v2/answer",
|
||||||
|
"/v2/baike",
|
||||||
|
"/v2/bili",
|
||||||
|
"/v2/bing",
|
||||||
|
"/v2/changya",
|
||||||
|
"/v2/chemical",
|
||||||
|
"/v2/douyin",
|
||||||
|
"/v2/duanzi",
|
||||||
|
"/v2/epic",
|
||||||
|
"/v2/exchange_rate",
|
||||||
|
"/v2/fabing",
|
||||||
|
"/v2/hitokoto",
|
||||||
|
"/v2/ip",
|
||||||
|
"/v2/kfc",
|
||||||
|
"/v2/luck",
|
||||||
|
"/v2/maoyan",
|
||||||
|
"/v2/today_in_history",
|
||||||
|
"/v2/toutiao",
|
||||||
|
"/v2/weibo",
|
||||||
|
"/v2/zhihu",
|
||||||
|
"/v2/lunar",
|
||||||
|
"/v2/ai-news",
|
||||||
|
"/v2/awesome-js",
|
||||||
|
"/v2/qrcode",
|
||||||
|
"/v2/dad-joke",
|
||||||
|
"/v2/hacker-news/:type",
|
||||||
|
"/v2/og",
|
||||||
|
"/v2/hash",
|
||||||
|
"/v2/fanyi",
|
||||||
|
"/v2/fanyi/langs",
|
||||||
|
"/v2/weather",
|
||||||
|
"/v2/weather/forecast",
|
||||||
|
"/v2/ncm-rank",
|
||||||
|
"/v2/ncm-rank/:id"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
"code": 200,
|
"code": 200,
|
||||||
"message": "获取成功。数据来自官方/权威源头,以确保稳定与实时。开源地址 https://github.com/vikiboss/60s,反馈群 595941841",
|
"message": "获取成功。数据来自官方/权威源头,以确保稳定与实时。开源地址 https://github.com/vikiboss/60s,反馈群 595941841",
|
||||||
"data": {
|
"data": {
|
||||||
"index": 2862,
|
"index": 121,
|
||||||
"hitokoto": "你带上罪恶之冠,即使背负上所有罪恶和孤独,绝不让你受伤"
|
"content": "这个世界上谁最懂猪?蜘蛛(知猪)人。"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
90
frontend/60sapi/娱乐消遣/随机发病文学/css/background.css
Normal file
90
frontend/60sapi/娱乐消遣/随机发病文学/css/background.css
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
body {
|
||||||
|
background-color: #1a1a1a;
|
||||||
|
color: #e0e0e0;
|
||||||
|
font-family: 'Courier New', Courier, monospace;
|
||||||
|
overflow: hidden;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#bg-container {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: -2;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: transform 0.2s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.floating-emoji {
|
||||||
|
position: absolute;
|
||||||
|
user-select: none;
|
||||||
|
opacity: 0;
|
||||||
|
animation-iteration-count: infinite;
|
||||||
|
animation-timing-function: linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes float-top-to-bottom {
|
||||||
|
0% { transform: translateY(-10vh) rotate(0deg); opacity: 0; }
|
||||||
|
10%, 90% { opacity: 0.7; }
|
||||||
|
100% { transform: translateY(110vh) rotate(360deg); opacity: 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes float-bottom-to-top {
|
||||||
|
0% { transform: translateY(110vh) rotate(0deg); opacity: 0; }
|
||||||
|
10%, 90% { opacity: 0.7; }
|
||||||
|
100% { transform: translateY(-10vh) rotate(360deg); opacity: 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes float-left-to-right {
|
||||||
|
0% { transform: translateX(-10vw) rotate(0deg); opacity: 0; }
|
||||||
|
10%, 90% { opacity: 0.7; }
|
||||||
|
100% { transform: translateX(110vw) rotate(360deg); opacity: 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes float-right-to-left {
|
||||||
|
0% { transform: translateX(110vw) rotate(0deg); opacity: 0; }
|
||||||
|
10%, 90% { opacity: 0.7; }
|
||||||
|
100% { transform: translateX(-10vw) rotate(360deg); opacity: 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-fragment {
|
||||||
|
position: absolute;
|
||||||
|
font-size: 24px;
|
||||||
|
color: rgba(255, 0, 255, 0.4);
|
||||||
|
opacity: 0;
|
||||||
|
animation: float-fragment 15s linear infinite, fade-in-out 15s linear infinite;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes float-fragment {
|
||||||
|
0% { transform: translate(0, 0) rotate(0deg); }
|
||||||
|
25% { transform: translate(20px, 40px) rotate(15deg); }
|
||||||
|
50% { transform: translate(-30px, -10px) rotate(-10deg); }
|
||||||
|
75% { transform: translate(10px, -30px) rotate(5deg); }
|
||||||
|
100% { transform: translate(0, 0) rotate(0deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fade-in-out {
|
||||||
|
0%, 100% { opacity: 0; }
|
||||||
|
10%, 90% { opacity: 0.4; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.screen-crack {
|
||||||
|
position: absolute;
|
||||||
|
width: 200px;
|
||||||
|
height: 200px;
|
||||||
|
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200"><path d="M 100 100 L 0 0 M 100 100 L 200 0 M 100 100 L 50 200 M 100 100 L 150 200" stroke="rgba(255,255,255,0.5)" stroke-width="1" fill="none"/></svg>');
|
||||||
|
opacity: 0;
|
||||||
|
animation: flicker-crack 25s steps(1, end) infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes flicker-crack {
|
||||||
|
0%, 100% { opacity: 0; }
|
||||||
|
50% { opacity: 0.3; }
|
||||||
|
51% { opacity: 0; }
|
||||||
|
75% { opacity: 0.2; }
|
||||||
|
76% { opacity: 0; }
|
||||||
|
}
|
||||||
235
frontend/60sapi/娱乐消遣/随机发病文学/css/style.css
Normal file
235
frontend/60sapi/娱乐消遣/随机发病文学/css/style.css
Normal file
@@ -0,0 +1,235 @@
|
|||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 100vh;
|
||||||
|
padding: 20px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-card {
|
||||||
|
background: rgba(20, 20, 20, 0.7);
|
||||||
|
border: none;
|
||||||
|
padding: 40px;
|
||||||
|
max-width: 600px;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
backdrop-filter: blur(5px);
|
||||||
|
position: relative;
|
||||||
|
clip-path: polygon(2% 5%, 97% 0%, 100% 95%, 0% 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.body-animated .content-card {
|
||||||
|
animation: tremble 0.4s infinite, glitch-shadow 1.5s steps(1, end) infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes tremble {
|
||||||
|
0% { clip-path: polygon(2% 5%, 97% 0%, 100% 95%, 0% 100%); }
|
||||||
|
25% { clip-path: polygon(2% 5%, 98% 2%, 99% 100%, 1% 98%); }
|
||||||
|
50% { clip-path: polygon(3% 4%, 96% 1%, 100% 96%, 2% 100%); }
|
||||||
|
75% { clip-path: polygon(1% 6%, 97% 3%, 98% 95%, 0% 99%); }
|
||||||
|
100% { clip-path: polygon(2% 5%, 97% 0%, 100% 95%, 0% 100%); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes glitch-shadow {
|
||||||
|
0% {
|
||||||
|
box-shadow:
|
||||||
|
0 0 8px rgba(255, 0, 255, 0.5),
|
||||||
|
inset 0 0 8px rgba(255, 0, 255, 0.4);
|
||||||
|
}
|
||||||
|
33% {
|
||||||
|
box-shadow:
|
||||||
|
0 0 8px rgba(0, 255, 255, 0.5),
|
||||||
|
inset 0 0 8px rgba(0, 255, 255, 0.4);
|
||||||
|
}
|
||||||
|
66% {
|
||||||
|
box-shadow:
|
||||||
|
0 0 8px rgba(0, 255, 0, 0.5),
|
||||||
|
inset 0 0 8px rgba(0, 255, 0, 0.4);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
box-shadow:
|
||||||
|
0 0 8px rgba(255, 0, 255, 0.5),
|
||||||
|
inset 0 0 8px rgba(255, 0, 255, 0.4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#literature-text {
|
||||||
|
font-size: 1.2em;
|
||||||
|
line-height: 1.8;
|
||||||
|
min-height: 100px;
|
||||||
|
color: #e0e0e0;
|
||||||
|
text-shadow: 0 0 5px rgba(0, 255, 135, 0.5);
|
||||||
|
animation: text-flicker 15s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.body-animated #literature-text {
|
||||||
|
animation: text-flicker 15s linear infinite, text-shadow-glitch 2s steps(1, end) infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes text-flicker {
|
||||||
|
0%, 100% { opacity: 1; }
|
||||||
|
50.0% { opacity: 0.95; }
|
||||||
|
50.5% { opacity: 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes text-shadow-glitch {
|
||||||
|
0% {
|
||||||
|
text-shadow:
|
||||||
|
1px 0 0 rgba(255,0,255,0.5),
|
||||||
|
-1px 0 0 rgba(0,255,255,0.5);
|
||||||
|
}
|
||||||
|
10% {
|
||||||
|
text-shadow:
|
||||||
|
-1px 0 0 rgba(255,0,255,0.5),
|
||||||
|
1px 0 0 rgba(0,255,255,0.5);
|
||||||
|
}
|
||||||
|
11%, 100% {
|
||||||
|
text-shadow: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.controls {
|
||||||
|
margin-top: 30px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#new-literature-btn {
|
||||||
|
background-color: #ff00ff;
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
padding: 12px 25px;
|
||||||
|
font-size: 1em;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 5px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-weight: bold;
|
||||||
|
transition: transform 0.2s, box-shadow 0.2s;
|
||||||
|
box-shadow: 0 0 10px #ff00ff, 0 0 20px #ff00ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
#new-literature-btn:hover {
|
||||||
|
transform: scale(1.05);
|
||||||
|
box-shadow: 0 0 15px #ff00ff, 0 0 30px #ff00ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
#new-literature-btn:active {
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Animation Toggle Switch */
|
||||||
|
.switch-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
color: #aaa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.switch {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
width: 50px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.switch input {
|
||||||
|
opacity: 0;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider {
|
||||||
|
position: absolute;
|
||||||
|
cursor: pointer;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: #ccc;
|
||||||
|
transition: .4s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider:before {
|
||||||
|
position: absolute;
|
||||||
|
content: "";
|
||||||
|
height: 16px;
|
||||||
|
width: 16px;
|
||||||
|
left: 4px;
|
||||||
|
bottom: 4px;
|
||||||
|
background-color: white;
|
||||||
|
transition: .4s;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:checked + .slider {
|
||||||
|
background-color: #ff00ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:checked + .slider:before {
|
||||||
|
transform: translateX(26px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider.round {
|
||||||
|
border-radius: 34px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider.round:before {
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Glitch Overlay & Animations */
|
||||||
|
#glitch-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: -1;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.body-animated #glitch-overlay {
|
||||||
|
animation: color-shift 15s steps(1, end) infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes color-shift {
|
||||||
|
0%, 100% { background: transparent; }
|
||||||
|
10% { background: rgba(255, 0, 0, 0.05); }
|
||||||
|
10.1% { background: transparent; }
|
||||||
|
20% { background: rgba(0, 255, 0, 0.05); }
|
||||||
|
20.1% { background: transparent; }
|
||||||
|
30% { background: rgba(0, 0, 255, 0.05); }
|
||||||
|
30.1% { background: transparent; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.flicker-block {
|
||||||
|
position: absolute;
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.body-animated .flicker-block {
|
||||||
|
animation: flicker 3s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes flicker {
|
||||||
|
0%, 100% { opacity: 0; }
|
||||||
|
50% { opacity: 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive Design */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.content-card {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
#literature-text {
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
.controls {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
}
|
||||||
34
frontend/60sapi/娱乐消遣/随机发病文学/index.html
Normal file
34
frontend/60sapi/娱乐消遣/随机发病文学/index.html
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>随机发病文学</title>
|
||||||
|
<link rel="stylesheet" href="css/background.css">
|
||||||
|
<link rel="stylesheet" href="css/style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="bg-container">
|
||||||
|
</div>
|
||||||
|
<div id="glitch-overlay"></div>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="content-card">
|
||||||
|
<p id="literature-text">正在加载发病文学...</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="controls">
|
||||||
|
<button id="new-literature-btn">再疯一次</button>
|
||||||
|
<div class="switch-container">
|
||||||
|
<label for="animation-toggle">关闭动画</label>
|
||||||
|
<label class="switch">
|
||||||
|
<input type="checkbox" id="animation-toggle" checked>
|
||||||
|
<span class="slider round"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="js/script.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
147
frontend/60sapi/娱乐消遣/随机发病文学/js/script.js
Normal file
147
frontend/60sapi/娱乐消遣/随机发病文学/js/script.js
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
const literatureTextElem = document.getElementById('literature-text');
|
||||||
|
const newLiteratureBtn = document.getElementById('new-literature-btn');
|
||||||
|
const animationToggle = document.getElementById('animation-toggle');
|
||||||
|
const bgContainer = document.getElementById('bg-container');
|
||||||
|
const body = document.body;
|
||||||
|
|
||||||
|
const apiEndpoints = [
|
||||||
|
'https://60s.api.shumengya.top/v2/fabing',
|
||||||
|
// Add fallback APIs here if available
|
||||||
|
];
|
||||||
|
|
||||||
|
let currentApiIndex = 0;
|
||||||
|
|
||||||
|
async function fetchLiterature() {
|
||||||
|
literatureTextElem.textContent = '正在卖力发疯中...';
|
||||||
|
literatureTextElem.style.opacity = '0.5';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(apiEndpoints[currentApiIndex]);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Network response was not ok');
|
||||||
|
}
|
||||||
|
const data = await response.json();
|
||||||
|
if (data.code === 200) {
|
||||||
|
literatureTextElem.textContent = data.data.saying;
|
||||||
|
} else {
|
||||||
|
throw new Error('API returned an error');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Fetch error:', error);
|
||||||
|
currentApiIndex = (currentApiIndex + 1) % apiEndpoints.length;
|
||||||
|
if (currentApiIndex !== 0) {
|
||||||
|
fetchLiterature(); // Retry with the next API
|
||||||
|
} else {
|
||||||
|
literatureTextElem.textContent = '疯不起来了,请稍后再试。';
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
literatureTextElem.style.opacity = '1';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createFloatingEmojis() {
|
||||||
|
const existingEmojis = bgContainer.querySelectorAll('.floating-emoji');
|
||||||
|
existingEmojis.forEach(e => e.remove());
|
||||||
|
|
||||||
|
const emojis = ['🤯', '😵', '🤪', '🥴', '🤡', '👹', '👻', '💀', '💥', '🔥', '🌪️', '😵💫'];
|
||||||
|
const animationNames = ['float-top-to-bottom', 'float-bottom-to-top', 'float-left-to-right', 'float-right-to-left'];
|
||||||
|
const emojiCount = 25;
|
||||||
|
|
||||||
|
for (let i = 0; i < emojiCount; i++) {
|
||||||
|
const emojiEl = document.createElement('div');
|
||||||
|
emojiEl.className = 'floating-emoji';
|
||||||
|
emojiEl.textContent = emojis[Math.floor(Math.random() * emojis.length)];
|
||||||
|
|
||||||
|
const animationName = animationNames[Math.floor(Math.random() * animationNames.length)];
|
||||||
|
emojiEl.style.animationName = animationName;
|
||||||
|
emojiEl.style.animationDuration = `${Math.random() * 10 + 15}s`; // 15-25 seconds
|
||||||
|
emojiEl.style.animationDelay = `${Math.random() * 20}s`;
|
||||||
|
emojiEl.style.fontSize = `${Math.random() * 20 + 20}px`;
|
||||||
|
|
||||||
|
// Set initial position based on animation direction
|
||||||
|
if (animationName.includes('top') || animationName.includes('bottom')) { // Vertical movement
|
||||||
|
emojiEl.style.left = `${Math.random() * 100}vw`;
|
||||||
|
} else { // Horizontal movement
|
||||||
|
emojiEl.style.top = `${Math.random() * 100}vh`;
|
||||||
|
}
|
||||||
|
|
||||||
|
bgContainer.appendChild(emojiEl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createFragments() {
|
||||||
|
const existingFragments = bgContainer.querySelectorAll('.text-fragment');
|
||||||
|
existingFragments.forEach(f => f.remove());
|
||||||
|
|
||||||
|
const fragments = ['我', '疯', '了', '?', '!', '…', '救命', '为什么', '好烦', '啊啊啊'];
|
||||||
|
for (let i = 0; i < 20; i++) {
|
||||||
|
const frag = document.createElement('div');
|
||||||
|
frag.className = 'text-fragment';
|
||||||
|
frag.textContent = fragments[Math.floor(Math.random() * fragments.length)];
|
||||||
|
frag.style.top = `${Math.random() * 100}%`;
|
||||||
|
frag.style.left = `${Math.random() * 100}%`;
|
||||||
|
frag.style.animationDelay = `${Math.random() * 15}s`;
|
||||||
|
frag.style.fontSize = `${Math.random() * 12 + 12}px`;
|
||||||
|
bgContainer.appendChild(frag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createCracks() {
|
||||||
|
for (let i = 0; i < 2; i++) {
|
||||||
|
const crack = document.createElement('div');
|
||||||
|
crack.className = 'screen-crack';
|
||||||
|
crack.style.top = `${Math.random() * 80}%`;
|
||||||
|
crack.style.left = `${Math.random() * 80}%`;
|
||||||
|
crack.style.transform = `rotate(${Math.random() * 360}deg)`;
|
||||||
|
crack.style.animationDelay = `${Math.random() * 25}s`;
|
||||||
|
bgContainer.appendChild(crack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createFlickerBlocks() {
|
||||||
|
const glitchOverlay = document.getElementById('glitch-overlay');
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
const block = document.createElement('div');
|
||||||
|
block.className = 'flicker-block';
|
||||||
|
block.style.width = `${Math.random() * 100 + 50}px`;
|
||||||
|
block.style.height = `${Math.random() * 100 + 50}px`;
|
||||||
|
block.style.top = `${Math.random() * 90}%`;
|
||||||
|
block.style.left = `${Math.random() * 90}%`;
|
||||||
|
block.style.animationDuration = `${Math.random() * 2 + 2}s`;
|
||||||
|
block.style.animationDelay = `${Math.random() * 3}s`;
|
||||||
|
glitchOverlay.appendChild(block);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleAnimations() {
|
||||||
|
if (animationToggle.checked) {
|
||||||
|
body.classList.add('body-animated');
|
||||||
|
} else {
|
||||||
|
body.classList.remove('body-animated');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('mousemove', (e) => {
|
||||||
|
if (!animationToggle.checked) return;
|
||||||
|
const x = (window.innerWidth / 2) - e.pageX;
|
||||||
|
const y = (window.innerHeight / 2) - e.pageY;
|
||||||
|
bgContainer.style.transform = `translateX(${x / 50}px) translateY(${y / 50}px)`;
|
||||||
|
});
|
||||||
|
|
||||||
|
newLiteratureBtn.addEventListener('click', fetchLiterature);
|
||||||
|
animationToggle.addEventListener('change', toggleAnimations);
|
||||||
|
|
||||||
|
// Initial setup
|
||||||
|
createFloatingEmojis();
|
||||||
|
createFragments();
|
||||||
|
createCracks();
|
||||||
|
createFlickerBlocks();
|
||||||
|
toggleAnimations();
|
||||||
|
fetchLiterature();
|
||||||
|
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
createFloatingEmojis();
|
||||||
|
createFragments();
|
||||||
|
});
|
||||||
|
});
|
||||||
47
frontend/60sapi/娱乐消遣/随机发病文学/接口集合.json
Normal file
47
frontend/60sapi/娱乐消遣/随机发病文学/接口集合.json
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
{
|
||||||
|
"api_name": "60s-api",
|
||||||
|
"api_version": "2.22.1",
|
||||||
|
"api_docs": "https://docs.60s-api.viki.moe",
|
||||||
|
"author": "Viki <hi@viki.moe>",
|
||||||
|
"user_group": "595941841",
|
||||||
|
"github_repo": "https://github.com/vikiboss/60s",
|
||||||
|
"updated": "2025/09/01 11:12:08",
|
||||||
|
"updated_at": 1756696328000,
|
||||||
|
"endpoints": [
|
||||||
|
"/v2/60s",
|
||||||
|
"/v2/answer",
|
||||||
|
"/v2/baike",
|
||||||
|
"/v2/bili",
|
||||||
|
"/v2/bing",
|
||||||
|
"/v2/changya",
|
||||||
|
"/v2/chemical",
|
||||||
|
"/v2/douyin",
|
||||||
|
"/v2/duanzi",
|
||||||
|
"/v2/epic",
|
||||||
|
"/v2/exchange_rate",
|
||||||
|
"/v2/fabing",
|
||||||
|
"/v2/hitokoto",
|
||||||
|
"/v2/ip",
|
||||||
|
"/v2/kfc",
|
||||||
|
"/v2/luck",
|
||||||
|
"/v2/maoyan",
|
||||||
|
"/v2/today_in_history",
|
||||||
|
"/v2/toutiao",
|
||||||
|
"/v2/weibo",
|
||||||
|
"/v2/zhihu",
|
||||||
|
"/v2/lunar",
|
||||||
|
"/v2/ai-news",
|
||||||
|
"/v2/awesome-js",
|
||||||
|
"/v2/qrcode",
|
||||||
|
"/v2/dad-joke",
|
||||||
|
"/v2/hacker-news/:type",
|
||||||
|
"/v2/og",
|
||||||
|
"/v2/hash",
|
||||||
|
"/v2/fanyi",
|
||||||
|
"/v2/fanyi/langs",
|
||||||
|
"/v2/weather",
|
||||||
|
"/v2/weather/forecast",
|
||||||
|
"/v2/ncm-rank",
|
||||||
|
"/v2/ncm-rank/:id"
|
||||||
|
]
|
||||||
|
}
|
||||||
8
frontend/60sapi/娱乐消遣/随机发病文学/返回接口.json
Normal file
8
frontend/60sapi/娱乐消遣/随机发病文学/返回接口.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "获取成功。数据来自官方/权威源头,以确保稳定与实时。开源地址 https://github.com/vikiboss/60s,反馈群 595941841",
|
||||||
|
"data": {
|
||||||
|
"index": 347,
|
||||||
|
"duanzi": "我不想读书,主要是因为家里牛啊,猪啊羊啊都没人喂。"
|
||||||
|
}
|
||||||
|
}
|
||||||
36
frontend/60sapi/娱乐消遣/随机搞笑段子/css/background.css
Normal file
36
frontend/60sapi/娱乐消遣/随机搞笑段子/css/background.css
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||||
|
margin: 0;
|
||||||
|
min-height: 100vh;
|
||||||
|
overflow-x: hidden;
|
||||||
|
transition: background 0.5s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hand-drawn Comic Theme Background - NEW VIBRANT VERSION */
|
||||||
|
body.theme-comic {
|
||||||
|
background: linear-gradient(-45deg, #ff7e5f, #feb47b, #ffcc80, #ffecb3);
|
||||||
|
background-size: 400% 400%;
|
||||||
|
animation: gradientBG 15s ease infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes gradientBG {
|
||||||
|
0% {
|
||||||
|
background-position: 0% 50%;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
background-position: 100% 50%;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
background-position: 0% 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Placeholder for Emoji Theme Background */
|
||||||
|
body.theme-emoji {
|
||||||
|
background-color: #fffde7;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Placeholder for Retro TV Theme Background */
|
||||||
|
body.theme-retro {
|
||||||
|
background-color: #3d2b1f;
|
||||||
|
}
|
||||||
199
frontend/60sapi/娱乐消遣/随机搞笑段子/css/style.css
Normal file
199
frontend/60sapi/娱乐消遣/随机搞笑段子/css/style.css
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
@import url('https://fonts.googleapis.com/css2?family=Zhi+Mang+Xing&display=swap');
|
||||||
|
|
||||||
|
/* --- General & Theme Switcher --- */
|
||||||
|
.container {
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-switcher {
|
||||||
|
position: fixed;
|
||||||
|
top: 15px;
|
||||||
|
right: 15px;
|
||||||
|
display: flex;
|
||||||
|
gap: 5px;
|
||||||
|
background: rgba(255, 255, 255, 0.8);
|
||||||
|
padding: 5px;
|
||||||
|
border-radius: 20px;
|
||||||
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-icon {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
border: 2px solid transparent;
|
||||||
|
}
|
||||||
|
.theme-icon.active {
|
||||||
|
border-color: #ff7043;
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Comic Theme Styles --- */
|
||||||
|
.theme-comic header h1 {
|
||||||
|
font-family: 'Zhi Mang Xing', cursive;
|
||||||
|
font-size: 4em;
|
||||||
|
color: #d84315; /* Deep Orange */
|
||||||
|
text-shadow: 2px 2px 0 #fff;
|
||||||
|
margin: 0.2em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-comic .divider {
|
||||||
|
height: 3px;
|
||||||
|
background: linear-gradient(90deg, #ffca28, #ff7043, #29b6f6, #66bb6a);
|
||||||
|
border-radius: 3px;
|
||||||
|
margin: 20px auto;
|
||||||
|
width: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-comic .joke-card {
|
||||||
|
background: rgba(255, 255, 255, 0.85); /* White with transparency */
|
||||||
|
backdrop-filter: blur(5px);
|
||||||
|
border-radius: 15px;
|
||||||
|
padding: 40px;
|
||||||
|
min-height: 200px;
|
||||||
|
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
position: relative;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
transform: rotate(-1deg);
|
||||||
|
transition: transform 0.2s ease;
|
||||||
|
}
|
||||||
|
.theme-comic .joke-card:hover {
|
||||||
|
transform: rotate(1deg) scale(1.02);
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-comic .joke-text {
|
||||||
|
font-family: 'Zhi Mang Xing', cursive;
|
||||||
|
font-size: 2em;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #5d4037;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-comic .new-joke-btn {
|
||||||
|
background: #1e88e5; /* Vibrant Blue */
|
||||||
|
color: white;
|
||||||
|
font-family: 'Zhi Mang Xing', cursive;
|
||||||
|
font-size: 2.5em;
|
||||||
|
border: none;
|
||||||
|
border-radius: 50px;
|
||||||
|
padding: 10px 30px;
|
||||||
|
cursor: pointer;
|
||||||
|
box-shadow: 0 5px 0 #1565c0; /* Darker Blue */
|
||||||
|
transition: all 0.1s ease-in-out;
|
||||||
|
}
|
||||||
|
.theme-comic .new-joke-btn:active {
|
||||||
|
transform: translateY(5px);
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Loading Animation --- */
|
||||||
|
.loading-container { display: none; }
|
||||||
|
.loading-container.visible { display: block; }
|
||||||
|
.loading-anim {
|
||||||
|
height: 60px;
|
||||||
|
width: 80px;
|
||||||
|
margin: 0 auto 10px;
|
||||||
|
}
|
||||||
|
.book {
|
||||||
|
transform-style: preserve-3d;
|
||||||
|
transform: rotateY(-30deg);
|
||||||
|
animation: flip 3s infinite;
|
||||||
|
}
|
||||||
|
.book, .book-page {
|
||||||
|
width: 40px;
|
||||||
|
height: 55px;
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
top: 50%;
|
||||||
|
margin-left: -20px;
|
||||||
|
margin-top: -27.5px;
|
||||||
|
}
|
||||||
|
.book-page {
|
||||||
|
background: #ffca28;
|
||||||
|
border: 1px solid #ff7043;
|
||||||
|
border-radius: 3px;
|
||||||
|
transform-origin: left;
|
||||||
|
}
|
||||||
|
.book-page:nth-child(1) { animation: flip-page 3s infinite; }
|
||||||
|
.book-page:nth-child(2) { animation: flip-page 3s -1s infinite; }
|
||||||
|
.book-page:nth-child(3) { animation: flip-page 3s -2s infinite; }
|
||||||
|
|
||||||
|
@keyframes flip { 50% { transform: rotateY(30deg); } }
|
||||||
|
@keyframes flip-page { 30%, 100% { transform: rotateY(180deg); } }
|
||||||
|
|
||||||
|
/* --- Feedback Buttons & Animations --- */
|
||||||
|
.feedback-buttons {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 15px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
.feedback-btn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
font-size: 2em;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: transform 0.2s ease;
|
||||||
|
}
|
||||||
|
.feedback-btn:hover { transform: scale(1.2); }
|
||||||
|
|
||||||
|
#animation-container {
|
||||||
|
position: fixed;
|
||||||
|
top: 0; left: 0; width: 100%; height: 100%;
|
||||||
|
pointer-events: none; z-index: 999;
|
||||||
|
}
|
||||||
|
.confetti, .snowflake {
|
||||||
|
position: absolute;
|
||||||
|
animation-timing-function: linear;
|
||||||
|
animation-iteration-count: infinite;
|
||||||
|
}
|
||||||
|
.confetti {
|
||||||
|
width: 10px; height: 10px;
|
||||||
|
animation-name: fall;
|
||||||
|
}
|
||||||
|
.snowflake {
|
||||||
|
font-size: 20px; color: #fff;
|
||||||
|
animation-name: fall;
|
||||||
|
}
|
||||||
|
@keyframes fall {
|
||||||
|
from { transform: translateY(-10vh) rotate(0deg); }
|
||||||
|
to { transform: translateY(110vh) rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.joke-card.absurd {
|
||||||
|
animation: absurd-flash 0.5s 2;
|
||||||
|
}
|
||||||
|
@keyframes absurd-flash {
|
||||||
|
0%, 100% { border: 2px solid transparent; }
|
||||||
|
50% { border: 5px solid red; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- General Joke Text Visibility --- */
|
||||||
|
.joke-text {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.9);
|
||||||
|
transition: opacity 0.4s ease, transform 0.4s ease;
|
||||||
|
}
|
||||||
|
.joke-text.visible {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Responsive --- */
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.theme-comic header h1 { font-size: 3em; }
|
||||||
|
.theme-comic .joke-card { padding: 25px; transform: rotate(0); }
|
||||||
|
.theme-comic .joke-card:hover { transform: rotate(0); }
|
||||||
|
.theme-comic .joke-text { font-size: 1.5em; }
|
||||||
|
}
|
||||||
59
frontend/60sapi/娱乐消遣/随机搞笑段子/index.html
Normal file
59
frontend/60sapi/娱乐消遣/随机搞笑段子/index.html
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>段子游乐场</title>
|
||||||
|
<link rel="stylesheet" href="css/background.css">
|
||||||
|
<link rel="stylesheet" href="css/style.css">
|
||||||
|
</head>
|
||||||
|
<body class="theme-comic"> <!-- Default Theme -->
|
||||||
|
|
||||||
|
<div class="theme-switcher">
|
||||||
|
<div class="theme-icon" data-theme="theme-comic" title="手绘漫画">✏️</div>
|
||||||
|
<div class="theme-icon" data-theme="theme-emoji" title="表情包狂欢">😂</div>
|
||||||
|
<div class="theme-icon" data-theme="theme-retro" title="复古电视">📺</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<header>
|
||||||
|
<h1>段子游乐场</h1>
|
||||||
|
<div class="divider"></div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<div id="joke-card" class="joke-card">
|
||||||
|
<div class="loading-container">
|
||||||
|
<div class="loading-anim">
|
||||||
|
<div class="book">
|
||||||
|
<div class="book-page"></div>
|
||||||
|
<div class="book-page"></div>
|
||||||
|
<div class="book-page"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p>段子菌正在翻笑话库...</p>
|
||||||
|
</div>
|
||||||
|
<p id="joke-text" class="joke-text"></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="feedback-buttons">
|
||||||
|
<button id="btn-lol" class="feedback-btn" title="笑到拍桌">🤣</button>
|
||||||
|
<button id="btn-cold" class="feedback-btn" title="有点冷">🥶</button>
|
||||||
|
<button id="btn-seen" class="feedback-btn" title="似曾相识">🤔</button>
|
||||||
|
<button id="btn-absurd" class="feedback-btn" title="离谱但好笑">🤯</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button id="new-joke-btn" class="new-joke-btn">
|
||||||
|
<span class="btn-text">再来一个!</span>
|
||||||
|
</button>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Animation & Sound Containers -->
|
||||||
|
<div id="animation-container"></div>
|
||||||
|
<audio id="sound-lol" src="https://www.myinstants.com/media/sounds/yay-6326.mp3" preload="auto"></audio>
|
||||||
|
<audio id="sound-cold" src="https://www.myinstants.com/media/sounds/zapsplat_cartoon_whoosh_fast_swoosh_001_76761.mp3" preload="auto"></audio>
|
||||||
|
|
||||||
|
<script src="js/script.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
122
frontend/60sapi/娱乐消遣/随机搞笑段子/js/script.js
Normal file
122
frontend/60sapi/娱乐消遣/随机搞笑段子/js/script.js
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
// Elements
|
||||||
|
const body = document.body;
|
||||||
|
const jokeTextElem = document.getElementById('joke-text');
|
||||||
|
const newJokeBtn = document.getElementById('new-joke-btn');
|
||||||
|
const loadingContainer = document.querySelector('.loading-container');
|
||||||
|
const animationContainer = document.getElementById('animation-container');
|
||||||
|
const jokeCard = document.getElementById('joke-card');
|
||||||
|
|
||||||
|
// API
|
||||||
|
const apiBaseUrls = ["https://60s.api.shumengya.top", "https://60s-cf.viki.moe", "https://60s.viki.moe"];
|
||||||
|
const apiPath = "/v2/duanzi";
|
||||||
|
let currentApiIndex = 0;
|
||||||
|
|
||||||
|
// --- Core Functions ---
|
||||||
|
const showLoading = (isLoading) => {
|
||||||
|
loadingContainer.classList.toggle('visible', isLoading);
|
||||||
|
if (isLoading) jokeTextElem.classList.remove('visible');
|
||||||
|
};
|
||||||
|
|
||||||
|
const displayJoke = (joke) => {
|
||||||
|
jokeTextElem.textContent = joke;
|
||||||
|
showLoading(false);
|
||||||
|
setTimeout(() => jokeTextElem.classList.add('visible'), 50);
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchJoke = async () => {
|
||||||
|
showLoading(true);
|
||||||
|
try {
|
||||||
|
const url = apiBaseUrls[currentApiIndex] + apiPath;
|
||||||
|
const response = await fetch(url, { timeout: 5000 });
|
||||||
|
if (!response.ok) throw new Error('Network response was not ok');
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
if (data.code === 200 && data.data && data.data.duanzi) {
|
||||||
|
displayJoke(data.data.duanzi);
|
||||||
|
} else {
|
||||||
|
throw new Error('Invalid data format');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`API error with ${apiBaseUrls[currentApiIndex]}:`, error);
|
||||||
|
currentApiIndex = (currentApiIndex + 1) % apiBaseUrls.length;
|
||||||
|
if (currentApiIndex !== 0) {
|
||||||
|
fetchJoke(); // Try next API
|
||||||
|
} else {
|
||||||
|
displayJoke('段子菌迷路了!点击‘再来一个’让它重新找路~');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- Theme Switcher ---
|
||||||
|
const themeSwitcher = document.querySelector('.theme-switcher');
|
||||||
|
themeSwitcher.addEventListener('click', (e) => {
|
||||||
|
if (e.target.classList.contains('theme-icon')) {
|
||||||
|
const theme = e.target.dataset.theme;
|
||||||
|
body.className = theme; // Set body class to the selected theme
|
||||||
|
|
||||||
|
// Update active icon
|
||||||
|
themeSwitcher.querySelectorAll('.theme-icon').forEach(icon => icon.classList.remove('active'));
|
||||||
|
e.target.classList.add('active');
|
||||||
|
|
||||||
|
alert(`主题已切换!部分主题(如表情包、复古电视)将在后续阶段实现。`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Set initial active theme icon
|
||||||
|
themeSwitcher.querySelector(`[data-theme="${body.className}"]`).classList.add('active');
|
||||||
|
|
||||||
|
|
||||||
|
// --- Feedback Buttons & Animations ---
|
||||||
|
const btnLol = document.getElementById('btn-lol');
|
||||||
|
const btnCold = document.getElementById('btn-cold');
|
||||||
|
const btnSeen = document.getElementById('btn-seen');
|
||||||
|
const btnAbsurd = document.getElementById('btn-absurd');
|
||||||
|
const soundLol = document.getElementById('sound-lol');
|
||||||
|
const soundCold = document.getElementById('sound-cold');
|
||||||
|
|
||||||
|
btnLol.addEventListener('click', () => {
|
||||||
|
soundLol.play();
|
||||||
|
createParticles(20, 'confetti');
|
||||||
|
});
|
||||||
|
|
||||||
|
btnCold.addEventListener('click', () => {
|
||||||
|
soundCold.play();
|
||||||
|
createParticles(15, 'snowflake');
|
||||||
|
});
|
||||||
|
|
||||||
|
btnSeen.addEventListener('click', () => {
|
||||||
|
displayJoke("原来你也听过!那再给你换个新鲜的~");
|
||||||
|
setTimeout(fetchJoke, 1500);
|
||||||
|
});
|
||||||
|
|
||||||
|
btnAbsurd.addEventListener('click', () => {
|
||||||
|
jokeCard.classList.add('absurd');
|
||||||
|
setTimeout(() => jokeCard.classList.remove('absurd'), 1000);
|
||||||
|
});
|
||||||
|
|
||||||
|
function createParticles(count, type) {
|
||||||
|
animationContainer.innerHTML = ''; // Clear previous
|
||||||
|
const colors = ['#ffca28', '#ff7043', '#29b6f6', '#66bb6a'];
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
const particle = document.createElement('div');
|
||||||
|
particle.classList.add(type);
|
||||||
|
if (type === 'confetti') {
|
||||||
|
particle.style.backgroundColor = colors[Math.floor(Math.random() * colors.length)];
|
||||||
|
} else {
|
||||||
|
particle.textContent = '❄️';
|
||||||
|
}
|
||||||
|
particle.style.left = `${Math.random() * 100}vw`;
|
||||||
|
const duration = Math.random() * 3 + 2; // 2-5 seconds
|
||||||
|
const delay = Math.random() * -duration; // Start at different times
|
||||||
|
particle.style.animationDuration = `${duration}s`;
|
||||||
|
particle.style.animationDelay = `${delay}s`;
|
||||||
|
animationContainer.appendChild(particle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Event Listeners ---
|
||||||
|
newJokeBtn.addEventListener('click', fetchJoke);
|
||||||
|
|
||||||
|
// --- Initial Load ---
|
||||||
|
fetchJoke();
|
||||||
|
});
|
||||||
47
frontend/60sapi/娱乐消遣/随机搞笑段子/接口集合.json
Normal file
47
frontend/60sapi/娱乐消遣/随机搞笑段子/接口集合.json
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
{
|
||||||
|
"api_name": "60s-api",
|
||||||
|
"api_version": "2.22.1",
|
||||||
|
"api_docs": "https://docs.60s-api.viki.moe",
|
||||||
|
"author": "Viki <hi@viki.moe>",
|
||||||
|
"user_group": "595941841",
|
||||||
|
"github_repo": "https://github.com/vikiboss/60s",
|
||||||
|
"updated": "2025/09/01 11:12:08",
|
||||||
|
"updated_at": 1756696328000,
|
||||||
|
"endpoints": [
|
||||||
|
"/v2/60s",
|
||||||
|
"/v2/answer",
|
||||||
|
"/v2/baike",
|
||||||
|
"/v2/bili",
|
||||||
|
"/v2/bing",
|
||||||
|
"/v2/changya",
|
||||||
|
"/v2/chemical",
|
||||||
|
"/v2/douyin",
|
||||||
|
"/v2/duanzi",
|
||||||
|
"/v2/epic",
|
||||||
|
"/v2/exchange_rate",
|
||||||
|
"/v2/fabing",
|
||||||
|
"/v2/hitokoto",
|
||||||
|
"/v2/ip",
|
||||||
|
"/v2/kfc",
|
||||||
|
"/v2/luck",
|
||||||
|
"/v2/maoyan",
|
||||||
|
"/v2/today_in_history",
|
||||||
|
"/v2/toutiao",
|
||||||
|
"/v2/weibo",
|
||||||
|
"/v2/zhihu",
|
||||||
|
"/v2/lunar",
|
||||||
|
"/v2/ai-news",
|
||||||
|
"/v2/awesome-js",
|
||||||
|
"/v2/qrcode",
|
||||||
|
"/v2/dad-joke",
|
||||||
|
"/v2/hacker-news/:type",
|
||||||
|
"/v2/og",
|
||||||
|
"/v2/hash",
|
||||||
|
"/v2/fanyi",
|
||||||
|
"/v2/fanyi/langs",
|
||||||
|
"/v2/weather",
|
||||||
|
"/v2/weather/forecast",
|
||||||
|
"/v2/ncm-rank",
|
||||||
|
"/v2/ncm-rank/:id"
|
||||||
|
]
|
||||||
|
}
|
||||||
8
frontend/60sapi/娱乐消遣/随机搞笑段子/返回接口.json
Normal file
8
frontend/60sapi/娱乐消遣/随机搞笑段子/返回接口.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "获取成功。数据来自官方/权威源头,以确保稳定与实时。开源地址 https://github.com/vikiboss/60s,反馈群 595941841",
|
||||||
|
"data": {
|
||||||
|
"index": 347,
|
||||||
|
"duanzi": "我不想读书,主要是因为家里牛啊,猪啊羊啊都没人喂。"
|
||||||
|
}
|
||||||
|
}
|
||||||
26
frontend/60sapi/娱乐消遣/随机运势/css/background.css
Normal file
26
frontend/60sapi/娱乐消遣/随机运势/css/background.css
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
body {
|
||||||
|
background: linear-gradient(-45deg, #0a021a, #2a0d3f, #4a1a6c, #7b2f8f);
|
||||||
|
background-size: 400% 400%;
|
||||||
|
animation: gradientBG 20s ease infinite;
|
||||||
|
color: #ffffff;
|
||||||
|
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 100vh;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes gradientBG {
|
||||||
|
0% {
|
||||||
|
background-position: 0% 50%;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
background-position: 100% 50%;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
background-position: 0% 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
342
frontend/60sapi/娱乐消遣/随机运势/css/style.css
Normal file
342
frontend/60sapi/娱乐消遣/随机运势/css/style.css
Normal file
@@ -0,0 +1,342 @@
|
|||||||
|
.container {
|
||||||
|
text-align: center;
|
||||||
|
padding: 20px;
|
||||||
|
max-width: 600px;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
header h1 {
|
||||||
|
font-size: 2.8em;
|
||||||
|
color: #f0e6ff;
|
||||||
|
text-shadow: 0 0 10px #d1a9ff, 0 0 20px #d1a9ff;
|
||||||
|
margin-bottom: 0.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
header p {
|
||||||
|
font-size: 1.2em;
|
||||||
|
color: #e0c8ff;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.crystal-ball-container {
|
||||||
|
perspective: 1000px;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.crystal-ball {
|
||||||
|
width: 200px;
|
||||||
|
height: 200px;
|
||||||
|
background: radial-gradient(circle at 30% 30%, rgba(255, 255, 255, 0.6), rgba(200, 180, 255, 0.1));
|
||||||
|
border-radius: 50%;
|
||||||
|
margin: 0 auto;
|
||||||
|
position: relative;
|
||||||
|
box-shadow: 0 0 30px #c390ff, 0 0 60px #a060e0, inset 0 0 20px rgba(255, 220, 255, 0.3);
|
||||||
|
animation: float 6s ease-in-out infinite;
|
||||||
|
transform-style: preserve-3d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reflection {
|
||||||
|
width: 80px;
|
||||||
|
height: 40px;
|
||||||
|
background: rgba(255, 255, 255, 0.3);
|
||||||
|
border-radius: 50%;
|
||||||
|
position: absolute;
|
||||||
|
top: 20px;
|
||||||
|
left: 40px;
|
||||||
|
transform: rotate(-30deg);
|
||||||
|
filter: blur(5px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.swirl {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
width: 120%;
|
||||||
|
height: 120%;
|
||||||
|
background: linear-gradient(45deg, rgba(255, 192, 203, 0.1), rgba(128, 0, 128, 0.2));
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: swirl 10s linear infinite;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes float {
|
||||||
|
0%, 100% { transform: translateY(0); }
|
||||||
|
50% { transform: translateY(-20px); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes swirl {
|
||||||
|
from { transform: translate(-50%, -50%) rotate(0deg); }
|
||||||
|
to { transform: translate(-50%, -50%) rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.fortune-card {
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
border-radius: 15px;
|
||||||
|
padding: 30px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
min-height: 120px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.37);
|
||||||
|
transition: opacity 0.5s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fortune-content {
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.5s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fortune-content.visible {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#luck-desc {
|
||||||
|
font-size: 2em;
|
||||||
|
color: #ffc0cb;
|
||||||
|
margin: 0 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#luck-tip {
|
||||||
|
font-size: 1.1em;
|
||||||
|
color: #e0e0e0;
|
||||||
|
margin: 0;
|
||||||
|
padding-bottom: 20px; /* Add some space before the new details */
|
||||||
|
}
|
||||||
|
|
||||||
|
.fortune-details {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
margin-top: 20px;
|
||||||
|
padding-top: 20px;
|
||||||
|
border-top: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-item {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-item h3 {
|
||||||
|
font-size: 0.9em;
|
||||||
|
color: #ffc0cb;
|
||||||
|
margin: 0 0 5px;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-item p {
|
||||||
|
font-size: 1.2em;
|
||||||
|
margin: 0;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
#lucky-color {
|
||||||
|
display: inline-block;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 2px solid white;
|
||||||
|
box-shadow: 0 0 5px rgba(0, 0, 0, 0.5);
|
||||||
|
/* Remove the text content */
|
||||||
|
font-size: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tarot Card Styles */
|
||||||
|
.tarot-container {
|
||||||
|
margin-top: 40px;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tarot-container h2 {
|
||||||
|
font-size: 1.5em;
|
||||||
|
color: #f0e6ff;
|
||||||
|
text-shadow: 0 0 8px #d1a9ff;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tarot-card-container {
|
||||||
|
width: 180px;
|
||||||
|
height: 280px;
|
||||||
|
perspective: 1000px;
|
||||||
|
margin: 0 auto;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tarot-card-inner {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
transition: transform 0.8s;
|
||||||
|
transform-style: preserve-3d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tarot-card-container.flipped .tarot-card-inner {
|
||||||
|
transform: rotateY(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tarot-card-front,
|
||||||
|
.tarot-card-back {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
-webkit-backface-visibility: hidden;
|
||||||
|
backface-visibility: hidden;
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tarot-card-back {
|
||||||
|
background: linear-gradient(135deg, #4a1a6c, #2a0d3f);
|
||||||
|
border: 2px solid #d1a9ff;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 3em;
|
||||||
|
color: #d1a9ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tarot-card-back::after {
|
||||||
|
content: '✧'; /* A simple star symbol */
|
||||||
|
text-shadow: 0 0 10px #f0e6ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tarot-card-front {
|
||||||
|
background: linear-gradient(135deg, #3e165b, #592883);
|
||||||
|
border: 2px solid #d1a9ff;
|
||||||
|
color: white;
|
||||||
|
transform: rotateY(180deg);
|
||||||
|
padding: 20px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tarot-name {
|
||||||
|
font-size: 1.4em;
|
||||||
|
color: #ffc0cb;
|
||||||
|
margin: 0 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tarot-interpretation {
|
||||||
|
font-size: 0.9em;
|
||||||
|
text-align: center;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Side Decorations */
|
||||||
|
.side-decor {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 15vw;
|
||||||
|
height: 100vh;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-decor {
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-decor {
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.decor-symbol {
|
||||||
|
position: absolute;
|
||||||
|
color: rgba(209, 169, 255, 0.5);
|
||||||
|
text-shadow: 0 0 10px rgba(240, 230, 255, 0.7);
|
||||||
|
animation: floatSymbol 20s infinite ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes floatSymbol {
|
||||||
|
0%, 100% {
|
||||||
|
transform: translateY(0) rotate(0deg);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
25%, 75% {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: translateY(-20vh) rotate(180deg);
|
||||||
|
opacity: 0.3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#get-fortune-btn {
|
||||||
|
background: linear-gradient(45deg, #da70d6, #8a2be2);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 50px;
|
||||||
|
padding: 15px 30px;
|
||||||
|
font-size: 1.1em;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: transform 0.2s, box-shadow 0.2s;
|
||||||
|
box-shadow: 0 0 15px #c390ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
#get-fortune-btn:hover {
|
||||||
|
transform: scale(1.05);
|
||||||
|
box-shadow: 0 0 25px #d1a9ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
#get-fortune-btn:active {
|
||||||
|
transform: scale(0.98);
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-spinner {
|
||||||
|
border: 4px solid rgba(255, 255, 255, 0.2);
|
||||||
|
border-left-color: #ffc0cb;
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
display: none; /* Hidden by default */
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-spinner.visible {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
to { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
margin-top: 40px;
|
||||||
|
color: rgba(255, 255, 255, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive Design */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
header h1 {
|
||||||
|
font-size: 2.2em;
|
||||||
|
}
|
||||||
|
.crystal-ball {
|
||||||
|
width: 150px;
|
||||||
|
height: 150px;
|
||||||
|
}
|
||||||
|
.fortune-card {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
.fortune-details {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tarot-card-container {
|
||||||
|
width: 150px;
|
||||||
|
height: 233px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide side decor on smaller screens */
|
||||||
|
@media (max-width: 1200px) {
|
||||||
|
.side-decor {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
71
frontend/60sapi/娱乐消遣/随机运势/index.html
Normal file
71
frontend/60sapi/娱乐消遣/随机运势/index.html
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>水晶球占卜</title>
|
||||||
|
<link rel="stylesheet" href="css/background.css">
|
||||||
|
<link rel="stylesheet" href="css/style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="side-decor left-decor"></div>
|
||||||
|
<div class="container">
|
||||||
|
<header>
|
||||||
|
<h1>水晶球占卜</h1>
|
||||||
|
<p>洞察你今日的运势</p>
|
||||||
|
</header>
|
||||||
|
<main>
|
||||||
|
<div class="crystal-ball-container">
|
||||||
|
<div class="crystal-ball">
|
||||||
|
<div class="reflection"></div>
|
||||||
|
<div class="swirl"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="fortune-card" class="fortune-card">
|
||||||
|
<div class="loading-spinner"></div>
|
||||||
|
<div class="fortune-content">
|
||||||
|
<h2 id="luck-desc"></h2>
|
||||||
|
<p id="luck-tip"></p>
|
||||||
|
<div class="fortune-details">
|
||||||
|
<div class="detail-item">
|
||||||
|
<h3>今日咒语</h3>
|
||||||
|
<p id="fortune-summary"></p>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<h3>幸运色</h3>
|
||||||
|
<p id="lucky-color"></p>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<h3>幸运数字</h3>
|
||||||
|
<p id="lucky-number"></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- New Tarot Card Section -->
|
||||||
|
<div class="tarot-container">
|
||||||
|
<h2>每日塔罗指引</h2>
|
||||||
|
<div id="tarot-card" class="tarot-card-container">
|
||||||
|
<div class="tarot-card-inner">
|
||||||
|
<div class="tarot-card-back">
|
||||||
|
<!-- Back of the card design -->
|
||||||
|
</div>
|
||||||
|
<div class="tarot-card-front">
|
||||||
|
<h3 id="tarot-name"></h3>
|
||||||
|
<p id="tarot-interpretation"></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button id="get-fortune-btn">再次占卜</button>
|
||||||
|
</main>
|
||||||
|
<footer>
|
||||||
|
<p>仅供娱乐,祝您好运</p>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
<div class="side-decor right-decor"></div>
|
||||||
|
<script src="js/script.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
175
frontend/60sapi/娱乐消遣/随机运势/js/script.js
Normal file
175
frontend/60sapi/娱乐消遣/随机运势/js/script.js
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
const getFortuneBtn = document.getElementById('get-fortune-btn');
|
||||||
|
const fortuneCard = document.getElementById('fortune-card');
|
||||||
|
const fortuneContent = fortuneCard.querySelector('.fortune-content');
|
||||||
|
const luckDescElem = document.getElementById('luck-desc');
|
||||||
|
const luckTipElem = document.getElementById('luck-tip');
|
||||||
|
const fortuneSummaryElem = document.getElementById('fortune-summary');
|
||||||
|
const luckyColorElem = document.getElementById('lucky-color');
|
||||||
|
const luckyNumberElem = document.getElementById('lucky-number');
|
||||||
|
const loadingSpinner = fortuneCard.querySelector('.loading-spinner');
|
||||||
|
const tarotCardContainer = document.getElementById('tarot-card');
|
||||||
|
const tarotNameElem = document.getElementById('tarot-name');
|
||||||
|
const tarotInterpretationElem = document.getElementById('tarot-interpretation');
|
||||||
|
|
||||||
|
const apiBaseUrls = [
|
||||||
|
"https://60s.api.shumengya.top",
|
||||||
|
"https://60s-cf.viki.moe",
|
||||||
|
"https://60s.viki.moe",
|
||||||
|
"https://60s.b23.run",
|
||||||
|
"https://60s.114128.xyz",
|
||||||
|
"https://60s-cf.114128.xyz"
|
||||||
|
];
|
||||||
|
const apiPath = "/v2/luck";
|
||||||
|
|
||||||
|
const mantras = [
|
||||||
|
"顺其自然,皆是美好。",
|
||||||
|
"相信直觉,它知道方向。",
|
||||||
|
"每一次呼吸都是新的开始。",
|
||||||
|
"心怀感恩,好运自来。",
|
||||||
|
"拥抱变化,发现惊喜。",
|
||||||
|
"你的能量,超乎想象。",
|
||||||
|
"保持微笑,宇宙会回应你。"
|
||||||
|
];
|
||||||
|
|
||||||
|
const tarotDeck = [
|
||||||
|
{ name: "愚者", interpretation: "新的开始,无限的潜力,天真和自由。勇敢地迈出第一步。" },
|
||||||
|
{ name: "魔术师", interpretation: "创造力,意志力,显化。你拥有实现目标所需的一切资源。" },
|
||||||
|
{ name: "女祭司", interpretation: "直觉,潜意识,神秘。倾听你内心的声音,智慧在你之内。" },
|
||||||
|
{ name: "皇后", interpretation: "丰饶,母性,创造。享受生活的美好,与自然和谐相处。" },
|
||||||
|
{ name: "皇帝", interpretation: "权威,结构,控制。建立秩序和纪律,掌控你的生活。" },
|
||||||
|
{ name: "教皇", interpretation: "传统,信仰,灵性指导。寻求智慧和知识,遵循传统。" },
|
||||||
|
{ name: "恋人", interpretation: "爱,和谐,选择。做出与你内心价值观一致的决定。" },
|
||||||
|
{ name: "战车", interpretation: "胜利,决心,控制。以坚定的意志力克服障碍,勇往直前。" },
|
||||||
|
{ name: "力量", interpretation: "勇气,内在力量,同情。用温柔和耐心驯服内心的野兽。" },
|
||||||
|
{ name: "隐士", interpretation: "内省,孤独,寻求真理。花时间独处,向内寻求答案。" },
|
||||||
|
{ name: "命运之轮", interpretation: "变化,命运,转折点。生活总在变化,顺应潮流。" },
|
||||||
|
{ name: "正义", interpretation: "公平,真理,因果。为你的行为负责,寻求平衡。" },
|
||||||
|
{ name: "倒吊人", interpretation: "新的视角,顺从,牺牲。放手,从不同的角度看问题。" },
|
||||||
|
{ name: "死神", interpretation: "结束,转变,新生。一个周期的结束是另一个周期的开始。" },
|
||||||
|
{ name: "节制", interpretation: "平衡,和谐,耐心。融合对立的力量,找到中间道路。" },
|
||||||
|
{ name: "恶魔", interpretation: "束缚,物质主义,诱惑。认识到你的束缚,并寻求解放。" },
|
||||||
|
{ name: "塔", interpretation: "突变,启示,解放。旧的结构正在崩塌,为新的结构让路。" },
|
||||||
|
{ name: "星星", interpretation: "希望,灵感,平静。在黑暗之后,总有希望的曙光。" },
|
||||||
|
{ name: "月亮", interpretation: "幻觉,恐惧,潜意识。面对你的恐惧,相信你的直觉。" },
|
||||||
|
{ name: "太阳", interpretation: "成功,喜悦,活力。拥抱光明,享受生活的乐趣。" },
|
||||||
|
{ name: "审判", interpretation: "觉醒,重生,评估。一个反思和更新的时刻。" },
|
||||||
|
{ name: "世界", interpretation: "完成,整合,成就。一个旅程的成功结束,庆祝你的成就。" }
|
||||||
|
];
|
||||||
|
|
||||||
|
let currentApiIndex = 0;
|
||||||
|
|
||||||
|
const showLoading = (isLoading) => {
|
||||||
|
if (isLoading) {
|
||||||
|
fortuneContent.classList.remove('visible');
|
||||||
|
loadingSpinner.classList.add('visible');
|
||||||
|
} else {
|
||||||
|
loadingSpinner.classList.remove('visible');
|
||||||
|
setTimeout(() => {
|
||||||
|
fortuneContent.classList.add('visible');
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchFortune = async () => {
|
||||||
|
showLoading(true);
|
||||||
|
tarotCardContainer.classList.remove('flipped'); // Reset card on new fetch
|
||||||
|
|
||||||
|
try {
|
||||||
|
const url = apiBaseUrls[currentApiIndex] + apiPath;
|
||||||
|
const response = await fetch(url, { timeout: 5000 });
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Network response was not ok');
|
||||||
|
}
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.code === 200 && data.data) {
|
||||||
|
updateFortune(data.data);
|
||||||
|
drawTarotCard(); // Draw a tarot card on success
|
||||||
|
} else {
|
||||||
|
throw new Error('Invalid data format');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`API error with ${apiBaseUrls[currentApiIndex]}:`, error);
|
||||||
|
currentApiIndex = (currentApiIndex + 1) % apiBaseUrls.length;
|
||||||
|
if (currentApiIndex !== 0) {
|
||||||
|
fetchFortune(); // Try next API
|
||||||
|
} else {
|
||||||
|
displayError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateFortune = (data) => {
|
||||||
|
luckDescElem.textContent = data.luck_desc || '运势';
|
||||||
|
luckTipElem.textContent = data.luck_tip || '今日运势平平,保持好心情。';
|
||||||
|
|
||||||
|
// Generate and display additional details
|
||||||
|
fortuneSummaryElem.textContent = mantras[Math.floor(Math.random() * mantras.length)];
|
||||||
|
luckyColorElem.style.backgroundColor = `#${Math.floor(Math.random()*16777215).toString(16).padStart(6, '0')}`;
|
||||||
|
luckyNumberElem.textContent = Math.floor(Math.random() * 100);
|
||||||
|
|
||||||
|
showLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const displayError = () => {
|
||||||
|
luckDescElem.textContent = '占卜失败';
|
||||||
|
luckTipElem.textContent = '无法连接到星辰之力,请稍后再试。';
|
||||||
|
fortuneSummaryElem.textContent = '---';
|
||||||
|
luckyColorElem.style.backgroundColor = 'transparent';
|
||||||
|
luckyNumberElem.textContent = '-';
|
||||||
|
showLoading(false);
|
||||||
|
tarotNameElem.textContent = '指引中断';
|
||||||
|
tarotInterpretationElem.textContent = '星辰之力暂时无法连接。';
|
||||||
|
tarotCardContainer.classList.add('flipped'); // Show error on card
|
||||||
|
};
|
||||||
|
|
||||||
|
const drawTarotCard = () => {
|
||||||
|
const card = tarotDeck[Math.floor(Math.random() * tarotDeck.length)];
|
||||||
|
tarotNameElem.textContent = card.name;
|
||||||
|
tarotInterpretationElem.textContent = card.interpretation;
|
||||||
|
|
||||||
|
// Flip the card after a short delay to allow the main content to appear
|
||||||
|
setTimeout(() => {
|
||||||
|
tarotCardContainer.classList.add('flipped');
|
||||||
|
}, 500);
|
||||||
|
};
|
||||||
|
|
||||||
|
const createSideDecorations = () => {
|
||||||
|
const leftContainer = document.querySelector('.left-decor');
|
||||||
|
const rightContainer = document.querySelector('.right-decor');
|
||||||
|
if (!leftContainer || !rightContainer) return;
|
||||||
|
|
||||||
|
const symbols = ['✧', '✦', '☾', '✶', '✵', '✩', '✨'];
|
||||||
|
const symbolCount = 15; // Number of symbols per side
|
||||||
|
|
||||||
|
const createSymbols = (container) => {
|
||||||
|
for (let i = 0; i < symbolCount; i++) {
|
||||||
|
const symbol = document.createElement('span');
|
||||||
|
symbol.classList.add('decor-symbol');
|
||||||
|
symbol.textContent = symbols[Math.floor(Math.random() * symbols.length)];
|
||||||
|
|
||||||
|
// Randomize properties for a more natural look
|
||||||
|
symbol.style.top = `${Math.random() * 90}vh`;
|
||||||
|
symbol.style.left = `${Math.random() * 80}%`;
|
||||||
|
symbol.style.fontSize = `${Math.random() * 20 + 10}px`;
|
||||||
|
symbol.style.animationDelay = `${Math.random() * 20}s`;
|
||||||
|
symbol.style.animationDuration = `${Math.random() * 20 + 15}s`; // Duration between 15s and 35s
|
||||||
|
|
||||||
|
container.appendChild(symbol);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
createSymbols(leftContainer);
|
||||||
|
createSymbols(rightContainer);
|
||||||
|
};
|
||||||
|
|
||||||
|
getFortuneBtn.addEventListener('click', fetchFortune);
|
||||||
|
tarotCardContainer.addEventListener('click', () => {
|
||||||
|
tarotCardContainer.classList.toggle('flipped');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Initial actions on page load
|
||||||
|
fetchFortune();
|
||||||
|
createSideDecorations();
|
||||||
|
});
|
||||||
10
frontend/60sapi/娱乐消遣/随机运势/返回接口.json
Normal file
10
frontend/60sapi/娱乐消遣/随机运势/返回接口.json
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "获取成功。数据来自官方/权威源头,以确保稳定与实时。开源地址 https://github.com/vikiboss/60s,反馈群 595941841",
|
||||||
|
"data": {
|
||||||
|
"luck_desc": "恋愛運",
|
||||||
|
"luck_rank": 21,
|
||||||
|
"luck_tip": "闪亮的邂逅之日!顺其自然吧",
|
||||||
|
"luck_tip_index": 19
|
||||||
|
}
|
||||||
|
}
|
||||||
233
frontend/60sapi/实用功能/公网IP地址/css/background.css
Normal file
233
frontend/60sapi/实用功能/公网IP地址/css/background.css
Normal file
@@ -0,0 +1,233 @@
|
|||||||
|
/* 动态背景样式 */
|
||||||
|
body::before {
|
||||||
|
content: '';
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
z-index: -2;
|
||||||
|
}
|
||||||
|
|
||||||
|
body::after {
|
||||||
|
content: '';
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background:
|
||||||
|
radial-gradient(circle at 20% 80%, rgba(120, 119, 198, 0.3) 0%, transparent 50%),
|
||||||
|
radial-gradient(circle at 80% 20%, rgba(255, 119, 198, 0.3) 0%, transparent 50%),
|
||||||
|
radial-gradient(circle at 40% 40%, rgba(120, 219, 255, 0.3) 0%, transparent 50%);
|
||||||
|
z-index: -1;
|
||||||
|
animation: backgroundMove 20s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes backgroundMove {
|
||||||
|
0%, 100% {
|
||||||
|
background:
|
||||||
|
radial-gradient(circle at 20% 80%, rgba(120, 119, 198, 0.3) 0%, transparent 50%),
|
||||||
|
radial-gradient(circle at 80% 20%, rgba(255, 119, 198, 0.3) 0%, transparent 50%),
|
||||||
|
radial-gradient(circle at 40% 40%, rgba(120, 219, 255, 0.3) 0%, transparent 50%);
|
||||||
|
}
|
||||||
|
25% {
|
||||||
|
background:
|
||||||
|
radial-gradient(circle at 60% 30%, rgba(120, 119, 198, 0.3) 0%, transparent 50%),
|
||||||
|
radial-gradient(circle at 30% 70%, rgba(255, 119, 198, 0.3) 0%, transparent 50%),
|
||||||
|
radial-gradient(circle at 80% 80%, rgba(120, 219, 255, 0.3) 0%, transparent 50%);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
background:
|
||||||
|
radial-gradient(circle at 80% 60%, rgba(120, 119, 198, 0.3) 0%, transparent 50%),
|
||||||
|
radial-gradient(circle at 20% 30%, rgba(255, 119, 198, 0.3) 0%, transparent 50%),
|
||||||
|
radial-gradient(circle at 60% 70%, rgba(120, 219, 255, 0.3) 0%, transparent 50%);
|
||||||
|
}
|
||||||
|
75% {
|
||||||
|
background:
|
||||||
|
radial-gradient(circle at 40% 90%, rgba(120, 119, 198, 0.3) 0%, transparent 50%),
|
||||||
|
radial-gradient(circle at 70% 10%, rgba(255, 119, 198, 0.3) 0%, transparent 50%),
|
||||||
|
radial-gradient(circle at 20% 60%, rgba(120, 219, 255, 0.3) 0%, transparent 50%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 浮动粒子效果 */
|
||||||
|
.particles {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.particle {
|
||||||
|
position: absolute;
|
||||||
|
width: 4px;
|
||||||
|
height: 4px;
|
||||||
|
background: rgba(255, 255, 255, 0.5);
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: float 15s infinite linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
.particle:nth-child(1) {
|
||||||
|
left: 10%;
|
||||||
|
animation-delay: 0s;
|
||||||
|
animation-duration: 12s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.particle:nth-child(2) {
|
||||||
|
left: 20%;
|
||||||
|
animation-delay: 2s;
|
||||||
|
animation-duration: 18s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.particle:nth-child(3) {
|
||||||
|
left: 30%;
|
||||||
|
animation-delay: 4s;
|
||||||
|
animation-duration: 15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.particle:nth-child(4) {
|
||||||
|
left: 40%;
|
||||||
|
animation-delay: 6s;
|
||||||
|
animation-duration: 20s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.particle:nth-child(5) {
|
||||||
|
left: 50%;
|
||||||
|
animation-delay: 8s;
|
||||||
|
animation-duration: 14s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.particle:nth-child(6) {
|
||||||
|
left: 60%;
|
||||||
|
animation-delay: 10s;
|
||||||
|
animation-duration: 16s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.particle:nth-child(7) {
|
||||||
|
left: 70%;
|
||||||
|
animation-delay: 12s;
|
||||||
|
animation-duration: 22s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.particle:nth-child(8) {
|
||||||
|
left: 80%;
|
||||||
|
animation-delay: 14s;
|
||||||
|
animation-duration: 13s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.particle:nth-child(9) {
|
||||||
|
left: 90%;
|
||||||
|
animation-delay: 16s;
|
||||||
|
animation-duration: 19s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.particle:nth-child(10) {
|
||||||
|
left: 15%;
|
||||||
|
animation-delay: 18s;
|
||||||
|
animation-duration: 17s;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes float {
|
||||||
|
0% {
|
||||||
|
transform: translateY(100vh) rotate(0deg);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
10% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
90% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translateY(-100px) rotate(360deg);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 网格背景效果 */
|
||||||
|
.grid-background {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-image:
|
||||||
|
linear-gradient(rgba(255, 255, 255, 0.1) 1px, transparent 1px),
|
||||||
|
linear-gradient(90deg, rgba(255, 255, 255, 0.1) 1px, transparent 1px);
|
||||||
|
background-size: 50px 50px;
|
||||||
|
z-index: -1;
|
||||||
|
opacity: 0.3;
|
||||||
|
animation: gridMove 30s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes gridMove {
|
||||||
|
0% {
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translate(50px, 50px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 光晕效果 */
|
||||||
|
.glow-effect {
|
||||||
|
position: fixed;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
width: 300px;
|
||||||
|
height: 300px;
|
||||||
|
background: radial-gradient(circle, rgba(74, 144, 226, 0.2) 0%, transparent 70%);
|
||||||
|
border-radius: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
z-index: -1;
|
||||||
|
animation: pulse 4s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse {
|
||||||
|
0%, 100% {
|
||||||
|
transform: translate(-50%, -50%) scale(1);
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: translate(-50%, -50%) scale(1.2);
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 响应式背景调整 */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.grid-background {
|
||||||
|
background-size: 30px 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.glow-effect {
|
||||||
|
width: 200px;
|
||||||
|
height: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.particle {
|
||||||
|
width: 3px;
|
||||||
|
height: 3px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.grid-background {
|
||||||
|
background-size: 20px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.glow-effect {
|
||||||
|
width: 150px;
|
||||||
|
height: 150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.particle {
|
||||||
|
width: 2px;
|
||||||
|
height: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
445
frontend/60sapi/实用功能/公网IP地址/css/style.css
Normal file
445
frontend/60sapi/实用功能/公网IP地址/css/style.css
Normal file
@@ -0,0 +1,445 @@
|
|||||||
|
/* 全局样式重置 */
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #333;
|
||||||
|
min-height: 100vh;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 容器样式 */
|
||||||
|
.container {
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 头部样式 */
|
||||||
|
.header {
|
||||||
|
text-align: center;
|
||||||
|
padding: 3rem 2rem 2rem;
|
||||||
|
background: linear-gradient(135deg, rgba(74, 144, 226, 0.1), rgba(80, 200, 120, 0.1));
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header h1 {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
font-weight: 700;
|
||||||
|
background: linear-gradient(135deg, #4a90e2, #50c878);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
background-clip: text;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header h1 i {
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
background: linear-gradient(135deg, #4a90e2, #50c878);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
background-clip: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
color: #666;
|
||||||
|
font-weight: 300;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 主要内容区域 */
|
||||||
|
.main-content {
|
||||||
|
flex: 1;
|
||||||
|
padding: 2rem;
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 查询按钮区域 */
|
||||||
|
.query-section {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.query-btn {
|
||||||
|
background: linear-gradient(135deg, #4a90e2, #50c878);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 1rem 2rem;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
border-radius: 50px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
box-shadow: 0 4px 15px rgba(74, 144, 226, 0.3);
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
min-width: 200px;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.query-btn:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 6px 20px rgba(74, 144, 226, 0.4);
|
||||||
|
background: linear-gradient(135deg, #3a7bc8, #40a868);
|
||||||
|
}
|
||||||
|
|
||||||
|
.query-btn:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.query-btn:disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
cursor: not-allowed;
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 加载动画 */
|
||||||
|
.loading {
|
||||||
|
text-align: center;
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner {
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
border: 4px solid #f3f3f3;
|
||||||
|
border-top: 4px solid #4a90e2;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
margin: 0 auto 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
0% { transform: rotate(0deg); }
|
||||||
|
100% { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading p {
|
||||||
|
color: #666;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* IP信息卡片 */
|
||||||
|
.ip-info {
|
||||||
|
animation: fadeInUp 0.6s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeInUp {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(30px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ip-card {
|
||||||
|
background: rgba(255, 255, 255, 0.9);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 2rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ip-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
padding-bottom: 1rem;
|
||||||
|
border-bottom: 2px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ip-header i {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
color: #4a90e2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ip-header h2 {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ip-display {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 1rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
padding: 1.5rem;
|
||||||
|
background: linear-gradient(135deg, rgba(74, 144, 226, 0.1), rgba(80, 200, 120, 0.1));
|
||||||
|
border-radius: 15px;
|
||||||
|
border: 2px solid rgba(74, 144, 226, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ip-address {
|
||||||
|
font-size: 2rem;
|
||||||
|
font-weight: 700;
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
color: #2c3e50;
|
||||||
|
background: linear-gradient(135deg, #4a90e2, #50c878);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
background-clip: text;
|
||||||
|
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.copy-btn {
|
||||||
|
background: #4a90e2;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 0.5rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
font-size: 1rem;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.copy-btn:hover {
|
||||||
|
background: #3a7bc8;
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ip-details {
|
||||||
|
display: grid;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
padding: 0.75rem;
|
||||||
|
background: rgba(248, 249, 250, 0.8);
|
||||||
|
border-radius: 10px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-item:hover {
|
||||||
|
background: rgba(74, 144, 226, 0.1);
|
||||||
|
transform: translateX(5px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-item i {
|
||||||
|
color: #4a90e2;
|
||||||
|
width: 20px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-item .label {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #555;
|
||||||
|
min-width: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-item .value {
|
||||||
|
color: #333;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* IP地址说明区域 */
|
||||||
|
.ip-explanation {
|
||||||
|
background: rgba(255, 255, 255, 0.9);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 2rem;
|
||||||
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ip-explanation h3 {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
font-size: 1.3rem;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ip-explanation h3 i {
|
||||||
|
color: #4a90e2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ip-explanation p {
|
||||||
|
color: #666;
|
||||||
|
line-height: 1.8;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.features {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 1rem;
|
||||||
|
padding: 1rem;
|
||||||
|
background: rgba(248, 249, 250, 0.8);
|
||||||
|
border-radius: 12px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-item:hover {
|
||||||
|
background: rgba(74, 144, 226, 0.1);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-item i {
|
||||||
|
color: #4a90e2;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
margin-top: 0.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-item h4 {
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 0.3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-item p {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: #666;
|
||||||
|
line-height: 1.5;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 错误信息 */
|
||||||
|
.error-message {
|
||||||
|
text-align: center;
|
||||||
|
padding: 2rem;
|
||||||
|
background: rgba(255, 255, 255, 0.9);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
border-radius: 20px;
|
||||||
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
|
||||||
|
border: 1px solid rgba(255, 99, 99, 0.3);
|
||||||
|
animation: fadeInUp 0.6s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-message i {
|
||||||
|
font-size: 3rem;
|
||||||
|
color: #ff6b6b;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-message p {
|
||||||
|
color: #666;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.retry-btn {
|
||||||
|
background: #ff6b6b;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 0.75rem 1.5rem;
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
border-radius: 25px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.retry-btn:hover {
|
||||||
|
background: #ff5252;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 15px rgba(255, 107, 107, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 页脚 */
|
||||||
|
.footer {
|
||||||
|
text-align: center;
|
||||||
|
padding: 2rem;
|
||||||
|
background: rgba(248, 249, 250, 0.8);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
border-top: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
|
color: #666;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 响应式设计 */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.header {
|
||||||
|
padding: 2rem 1rem 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header h1 {
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-content {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ip-card, .ip-explanation {
|
||||||
|
padding: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ip-address {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ip-display {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.features {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-item {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 0.3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-item .label {
|
||||||
|
min-width: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.header h1 {
|
||||||
|
font-size: 1.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.query-btn {
|
||||||
|
padding: 0.875rem 1.5rem;
|
||||||
|
font-size: 1rem;
|
||||||
|
min-width: 180px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ip-address {
|
||||||
|
font-size: 1.3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ip-card, .ip-explanation {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
139
frontend/60sapi/实用功能/公网IP地址/index.html
Normal file
139
frontend/60sapi/实用功能/公网IP地址/index.html
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>公网IP地址查询</title>
|
||||||
|
<link rel="stylesheet" href="css/style.css">
|
||||||
|
<link rel="stylesheet" href="css/background.css">
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<!-- 头部 -->
|
||||||
|
<header class="header">
|
||||||
|
<h1><i class="fas fa-globe"></i> 公网IP地址查询</h1>
|
||||||
|
<p class="subtitle">快速获取您的公网IP地址信息</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<!-- 主要内容区域 -->
|
||||||
|
<main class="main-content">
|
||||||
|
<!-- 查询按钮区域 -->
|
||||||
|
<div class="query-section">
|
||||||
|
<button id="queryBtn" class="query-btn">
|
||||||
|
<i class="fas fa-search"></i>
|
||||||
|
<span>查询我的IP地址</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 加载动画 -->
|
||||||
|
<div id="loading" class="loading" style="display: none;">
|
||||||
|
<div class="spinner"></div>
|
||||||
|
<p>正在获取IP地址信息...</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- IP信息展示区域 -->
|
||||||
|
<div id="ip-info" class="ip-info" style="display: none;">
|
||||||
|
<div class="ip-card">
|
||||||
|
<div class="ip-header">
|
||||||
|
<i class="fas fa-network-wired"></i>
|
||||||
|
<h2>您的公网IP地址</h2>
|
||||||
|
</div>
|
||||||
|
<div class="ip-display">
|
||||||
|
<span id="ip-address" class="ip-address">---.---.---.---</span>
|
||||||
|
<button id="copyBtn" class="copy-btn" title="复制IP地址">
|
||||||
|
<i class="fas fa-copy"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="ip-details">
|
||||||
|
<div class="detail-item">
|
||||||
|
<i class="fas fa-clock"></i>
|
||||||
|
<span class="label">查询时间:</span>
|
||||||
|
<span id="query-time" class="value">--</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<i class="fas fa-server"></i>
|
||||||
|
<span class="label">数据来源:</span>
|
||||||
|
<span class="value">60s.viki.moe</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<i class="fas fa-map-marker-alt"></i>
|
||||||
|
<span class="label">位置信息:</span>
|
||||||
|
<span id="location" class="value">--</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<i class="fas fa-building"></i>
|
||||||
|
<span class="label">网络服务商:</span>
|
||||||
|
<span id="isp" class="value">--</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<i class="fas fa-flag"></i>
|
||||||
|
<span class="label">国家:</span>
|
||||||
|
<span id="country" class="value">--</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<i class="fas fa-map"></i>
|
||||||
|
<span class="label">地区:</span>
|
||||||
|
<span id="region" class="value">--</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<i class="fas fa-city"></i>
|
||||||
|
<span class="label">城市:</span>
|
||||||
|
<span id="city" class="value">--</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<i class="fas fa-clock"></i>
|
||||||
|
<span class="label">时区:</span>
|
||||||
|
<span id="timezone" class="value">--</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- IP地址信息说明 -->
|
||||||
|
<div class="ip-explanation">
|
||||||
|
<h3><i class="fas fa-info-circle"></i> 什么是公网IP地址?</h3>
|
||||||
|
<p>公网IP地址是您的设备在互联网上的唯一标识符,由您的网络服务提供商(ISP)分配。通过这个地址,互联网上的其他设备可以找到并与您的设备通信。</p>
|
||||||
|
|
||||||
|
<div class="features">
|
||||||
|
<div class="feature-item">
|
||||||
|
<i class="fas fa-shield-alt"></i>
|
||||||
|
<div>
|
||||||
|
<h4>隐私保护</h4>
|
||||||
|
<p>了解您的IP地址有助于保护网络隐私</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="feature-item">
|
||||||
|
<i class="fas fa-map-marker-alt"></i>
|
||||||
|
<div>
|
||||||
|
<h4>地理位置</h4>
|
||||||
|
<p>IP地址可以大致确定您的地理位置</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="feature-item">
|
||||||
|
<i class="fas fa-cogs"></i>
|
||||||
|
<div>
|
||||||
|
<h4>网络配置</h4>
|
||||||
|
<p>用于网络故障排除和配置</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 错误信息 -->
|
||||||
|
<div id="error-message" class="error-message" style="display: none;">
|
||||||
|
<i class="fas fa-exclamation-triangle"></i>
|
||||||
|
<p>获取IP地址失败,请检查网络连接或稍后重试</p>
|
||||||
|
<button id="retryBtn" class="retry-btn">重试</button>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<!-- 页脚 -->
|
||||||
|
<footer class="footer">
|
||||||
|
<p>© 2024 公网IP地址查询工具 | 数据来源: 60s.viki.moe</p>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="js/script.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
315
frontend/60sapi/实用功能/公网IP地址/js/script.js
Normal file
315
frontend/60sapi/实用功能/公网IP地址/js/script.js
Normal file
@@ -0,0 +1,315 @@
|
|||||||
|
// 公网IP地址查询应用
|
||||||
|
class IPQueryApp {
|
||||||
|
constructor() {
|
||||||
|
this.apiEndpoint = 'https://60s.viki.moe/v2/ip';
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化应用
|
||||||
|
init() {
|
||||||
|
this.bindEvents();
|
||||||
|
this.createParticles();
|
||||||
|
this.createBackgroundElements();
|
||||||
|
console.log('IP查询应用初始化完成');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 绑定事件
|
||||||
|
bindEvents() {
|
||||||
|
const queryBtn = document.getElementById('queryBtn');
|
||||||
|
const retryBtn = document.getElementById('retryBtn');
|
||||||
|
const copyBtn = document.getElementById('copyBtn');
|
||||||
|
|
||||||
|
if (queryBtn) {
|
||||||
|
queryBtn.addEventListener('click', () => this.queryIP());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (retryBtn) {
|
||||||
|
retryBtn.addEventListener('click', () => this.queryIP());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (copyBtn) {
|
||||||
|
copyBtn.addEventListener('click', () => this.copyIP());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 页面加载完成后自动查询一次
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
setTimeout(() => this.queryIP(), 500);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建浮动粒子
|
||||||
|
createParticles() {
|
||||||
|
const particlesContainer = document.createElement('div');
|
||||||
|
particlesContainer.className = 'particles';
|
||||||
|
document.body.appendChild(particlesContainer);
|
||||||
|
|
||||||
|
for (let i = 0; i < 10; i++) {
|
||||||
|
const particle = document.createElement('div');
|
||||||
|
particle.className = 'particle';
|
||||||
|
particlesContainer.appendChild(particle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建背景元素
|
||||||
|
createBackgroundElements() {
|
||||||
|
// 创建网格背景
|
||||||
|
const gridBackground = document.createElement('div');
|
||||||
|
gridBackground.className = 'grid-background';
|
||||||
|
document.body.appendChild(gridBackground);
|
||||||
|
|
||||||
|
// 创建光晕效果
|
||||||
|
const glowEffect = document.createElement('div');
|
||||||
|
glowEffect.className = 'glow-effect';
|
||||||
|
document.body.appendChild(glowEffect);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示加载状态
|
||||||
|
showLoading() {
|
||||||
|
const loading = document.getElementById('loading');
|
||||||
|
const ipInfo = document.getElementById('ipInfo');
|
||||||
|
const errorMessage = document.getElementById('errorMessage');
|
||||||
|
const queryBtn = document.getElementById('queryBtn');
|
||||||
|
|
||||||
|
if (loading) loading.style.display = 'block';
|
||||||
|
if (ipInfo) ipInfo.style.display = 'none';
|
||||||
|
if (errorMessage) errorMessage.style.display = 'none';
|
||||||
|
if (queryBtn) {
|
||||||
|
queryBtn.disabled = true;
|
||||||
|
queryBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> 查询中...';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 隐藏加载状态
|
||||||
|
hideLoading() {
|
||||||
|
const loading = document.getElementById('loading');
|
||||||
|
const queryBtn = document.getElementById('queryBtn');
|
||||||
|
|
||||||
|
if (loading) loading.style.display = 'none';
|
||||||
|
if (queryBtn) {
|
||||||
|
queryBtn.disabled = false;
|
||||||
|
queryBtn.innerHTML = '<i class="fas fa-search"></i> 查询我的IP';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示错误信息
|
||||||
|
showError(message) {
|
||||||
|
const errorMessage = document.getElementById('error-message');
|
||||||
|
const ipInfo = document.getElementById('ip-info');
|
||||||
|
|
||||||
|
if (errorMessage) {
|
||||||
|
errorMessage.style.display = 'block';
|
||||||
|
const errorText = errorMessage.querySelector('p');
|
||||||
|
if (errorText) errorText.textContent = message || '获取IP信息失败,请稍后重试';
|
||||||
|
}
|
||||||
|
if (ipInfo) ipInfo.style.display = 'none';
|
||||||
|
|
||||||
|
this.hideLoading();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询IP地址
|
||||||
|
async queryIP() {
|
||||||
|
try {
|
||||||
|
this.showLoading();
|
||||||
|
console.log('开始查询IP地址...');
|
||||||
|
|
||||||
|
const response = await fetch(this.apiEndpoint, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('API响应状态:', response.status);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP错误: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
console.log('API返回数据:', data);
|
||||||
|
|
||||||
|
if (data.code === 200 && data.data) {
|
||||||
|
this.displayIPInfo(data.data);
|
||||||
|
} else {
|
||||||
|
throw new Error(data.message || '获取IP信息失败');
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('查询IP失败:', error);
|
||||||
|
this.showError(error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示IP信息
|
||||||
|
displayIPInfo(data) {
|
||||||
|
const ipInfo = document.getElementById('ip-info');
|
||||||
|
const errorMessage = document.getElementById('error-message');
|
||||||
|
|
||||||
|
// 更新IP地址显示
|
||||||
|
const ipAddressElement = document.getElementById('ip-address');
|
||||||
|
if (ipAddressElement && data.ip) {
|
||||||
|
ipAddressElement.textContent = data.ip;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新查询时间
|
||||||
|
const queryTimeElement = document.getElementById('query-time');
|
||||||
|
if (queryTimeElement) {
|
||||||
|
const now = new Date();
|
||||||
|
queryTimeElement.textContent = now.toLocaleString('zh-CN');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新详细信息
|
||||||
|
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 || '未知');
|
||||||
|
|
||||||
|
// 显示IP信息,隐藏错误信息
|
||||||
|
if (ipInfo) ipInfo.style.display = 'block';
|
||||||
|
if (errorMessage) errorMessage.style.display = 'none';
|
||||||
|
|
||||||
|
this.hideLoading();
|
||||||
|
console.log('IP信息显示完成');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新详细信息项
|
||||||
|
updateDetailItem(id, value) {
|
||||||
|
const element = document.getElementById(id);
|
||||||
|
if (element) {
|
||||||
|
element.textContent = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 复制IP地址
|
||||||
|
async copyIP() {
|
||||||
|
const ipAddressElement = document.getElementById('ip-address');
|
||||||
|
const copyBtn = document.getElementById('copyBtn');
|
||||||
|
|
||||||
|
if (!ipAddressElement || !ipAddressElement.textContent) {
|
||||||
|
this.showToast('没有可复制的IP地址', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(ipAddressElement.textContent);
|
||||||
|
|
||||||
|
// 更新按钮状态
|
||||||
|
if (copyBtn) {
|
||||||
|
const originalHTML = copyBtn.innerHTML;
|
||||||
|
copyBtn.innerHTML = '<i class="fas fa-check"></i>';
|
||||||
|
copyBtn.style.background = '#50c878';
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
copyBtn.innerHTML = originalHTML;
|
||||||
|
copyBtn.style.background = '#4a90e2';
|
||||||
|
}, 1500);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.showToast('IP地址已复制到剪贴板', 'success');
|
||||||
|
console.log('IP地址已复制:', ipAddressElement.textContent);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('复制失败:', error);
|
||||||
|
this.showToast('复制失败,请手动选择复制', 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示提示消息
|
||||||
|
showToast(message, type = 'info') {
|
||||||
|
// 移除已存在的toast
|
||||||
|
const existingToast = document.querySelector('.toast');
|
||||||
|
if (existingToast) {
|
||||||
|
existingToast.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建新的toast
|
||||||
|
const toast = document.createElement('div');
|
||||||
|
toast.className = `toast toast-${type}`;
|
||||||
|
toast.innerHTML = `
|
||||||
|
<i class="fas fa-${type === 'success' ? 'check-circle' : type === 'error' ? 'exclamation-circle' : 'info-circle'}"></i>
|
||||||
|
<span>${message}</span>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// 添加toast样式
|
||||||
|
toast.style.cssText = `
|
||||||
|
position: fixed;
|
||||||
|
top: 20px;
|
||||||
|
right: 20px;
|
||||||
|
background: ${type === 'success' ? '#50c878' : type === 'error' ? '#ff6b6b' : '#4a90e2'};
|
||||||
|
color: white;
|
||||||
|
padding: 1rem 1.5rem;
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
|
||||||
|
z-index: 1000;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
font-weight: 500;
|
||||||
|
animation: slideInRight 0.3s ease;
|
||||||
|
max-width: 300px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
// 添加动画样式
|
||||||
|
const style = document.createElement('style');
|
||||||
|
style.textContent = `
|
||||||
|
@keyframes slideInRight {
|
||||||
|
from {
|
||||||
|
transform: translateX(100%);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: translateX(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes slideOutRight {
|
||||||
|
from {
|
||||||
|
transform: translateX(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: translateX(100%);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
document.head.appendChild(style);
|
||||||
|
|
||||||
|
document.body.appendChild(toast);
|
||||||
|
|
||||||
|
// 3秒后自动移除
|
||||||
|
setTimeout(() => {
|
||||||
|
toast.style.animation = 'slideOutRight 0.3s ease';
|
||||||
|
setTimeout(() => {
|
||||||
|
if (toast.parentNode) {
|
||||||
|
toast.remove();
|
||||||
|
}
|
||||||
|
if (style.parentNode) {
|
||||||
|
style.remove();
|
||||||
|
}
|
||||||
|
}, 300);
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 格式化时间
|
||||||
|
formatTime(timestamp) {
|
||||||
|
const date = new Date(timestamp);
|
||||||
|
return date.toLocaleString('zh-CN', {
|
||||||
|
year: 'numeric',
|
||||||
|
month: '2-digit',
|
||||||
|
day: '2-digit',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
second: '2-digit'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化应用
|
||||||
|
const app = new IPQueryApp();
|
||||||
|
|
||||||
|
// 导出到全局作用域(用于调试)
|
||||||
|
window.IPQueryApp = app;
|
||||||
3
frontend/60sapi/实用功能/公网IP地址/接口集合.json
Normal file
3
frontend/60sapi/实用功能/公网IP地址/接口集合.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
[
|
||||||
|
"https://60s.viki.moe/v2/ip"
|
||||||
|
]
|
||||||
0
frontend/60sapi/实用功能/公网IP地址/返回接口.json
Normal file
0
frontend/60sapi/实用功能/公网IP地址/返回接口.json
Normal file
561
frontend/60sapi/实用功能/哈希解压压缩/css/style.css
Normal file
561
frontend/60sapi/实用功能/哈希解压压缩/css/style.css
Normal file
@@ -0,0 +1,561 @@
|
|||||||
|
/* Reset and Base Styles */
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
min-height: 100vh;
|
||||||
|
color: #333;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 1400px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Header Styles */
|
||||||
|
.header {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-content {
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
border-radius: 24px;
|
||||||
|
padding: 40px;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
|
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-content::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: linear-gradient(45deg, rgba(255, 255, 255, 0.1) 0%, transparent 50%, rgba(255, 255, 255, 0.1) 100%);
|
||||||
|
animation: shimmer 3s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes shimmer {
|
||||||
|
0%, 100% { transform: translateX(-100%); }
|
||||||
|
50% { transform: translateX(100%); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 15px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo i {
|
||||||
|
font-size: 48px;
|
||||||
|
background: linear-gradient(45deg, #ff6b6b, #4ecdc4);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
background-clip: text;
|
||||||
|
animation: pulse 2s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse {
|
||||||
|
0%, 100% { transform: scale(1); }
|
||||||
|
50% { transform: scale(1.1); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo h1 {
|
||||||
|
font-size: 42px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: white;
|
||||||
|
text-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
font-size: 18px;
|
||||||
|
color: rgba(255, 255, 255, 0.9);
|
||||||
|
font-weight: 400;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Floating Shapes */
|
||||||
|
.header-decoration {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.floating-shapes {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shape {
|
||||||
|
position: absolute;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: linear-gradient(45deg, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0.05));
|
||||||
|
animation: float 6s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shape-1 {
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
top: 10%;
|
||||||
|
left: 10%;
|
||||||
|
animation-delay: 0s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shape-2 {
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
top: 20%;
|
||||||
|
right: 15%;
|
||||||
|
animation-delay: 2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shape-3 {
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
bottom: 15%;
|
||||||
|
left: 20%;
|
||||||
|
animation-delay: 4s;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes float {
|
||||||
|
0%, 100% { transform: translateY(0px) rotate(0deg); }
|
||||||
|
33% { transform: translateY(-20px) rotate(120deg); }
|
||||||
|
66% { transform: translateY(10px) rotate(240deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Input Section */
|
||||||
|
.input-section {
|
||||||
|
margin-bottom: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-card {
|
||||||
|
background: rgba(255, 255, 255, 0.95);
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 30px;
|
||||||
|
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-card:hover {
|
||||||
|
transform: translateY(-5px);
|
||||||
|
box-shadow: 0 30px 60px rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header i {
|
||||||
|
font-size: 24px;
|
||||||
|
color: #667eea;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header h2 {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-wrapper {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
#inputText {
|
||||||
|
width: 100%;
|
||||||
|
padding: 20px;
|
||||||
|
border: 2px solid #e1e5e9;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-family: inherit;
|
||||||
|
resize: vertical;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
background: rgba(255, 255, 255, 0.8);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
#inputText:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #667eea;
|
||||||
|
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
|
||||||
|
background: rgba(255, 255, 255, 0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 15px;
|
||||||
|
margin-top: 20px;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Button Styles */
|
||||||
|
.btn {
|
||||||
|
padding: 12px 24px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
text-decoration: none;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: -100%;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
|
||||||
|
transition: left 0.5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:hover::before {
|
||||||
|
left: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
color: white;
|
||||||
|
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 8px 25px rgba(102, 126, 234, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary {
|
||||||
|
background: rgba(255, 255, 255, 0.8);
|
||||||
|
color: #666;
|
||||||
|
border: 1px solid #e1e5e9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.95);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Results Section */
|
||||||
|
.results-section {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(30px);
|
||||||
|
transition: all 0.5s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.results-section.show {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.results-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
|
||||||
|
gap: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-card {
|
||||||
|
background: rgba(255, 255, 255, 0.95);
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 30px;
|
||||||
|
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-card::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 4px;
|
||||||
|
background: linear-gradient(90deg, #ff6b6b, #4ecdc4, #45b7d1, #96ceb4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-card:hover {
|
||||||
|
transform: translateY(-5px);
|
||||||
|
box-shadow: 0 30px 60px rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-card .card-header h3 {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-items {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-item {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-item label {
|
||||||
|
display: block;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #666;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-value {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
background: rgba(248, 250, 252, 0.8);
|
||||||
|
border: 1px solid #e1e5e9;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||||
|
font-size: 14px;
|
||||||
|
word-break: break-all;
|
||||||
|
position: relative;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-value:hover {
|
||||||
|
background: rgba(248, 250, 252, 0.95);
|
||||||
|
border-color: #667eea;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-value .placeholder {
|
||||||
|
color: #999;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.copy-btn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: #667eea;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 8px;
|
||||||
|
border-radius: 6px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
margin-left: auto;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.copy-btn:hover {
|
||||||
|
background: rgba(102, 126, 234, 0.1);
|
||||||
|
color: #5a67d8;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Loading Overlay */
|
||||||
|
.loading-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
backdrop-filter: blur(5px);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 1000;
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-overlay.show {
|
||||||
|
opacity: 1;
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-spinner {
|
||||||
|
text-align: center;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner {
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
border: 4px solid rgba(255, 255, 255, 0.3);
|
||||||
|
border-top: 4px solid white;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
margin: 0 auto 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
0% { transform: rotate(0deg); }
|
||||||
|
100% { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Toast Notification */
|
||||||
|
.toast {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 30px;
|
||||||
|
right: 30px;
|
||||||
|
background: linear-gradient(135deg, #4ecdc4, #44a08d);
|
||||||
|
color: white;
|
||||||
|
padding: 16px 24px;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
transform: translateX(400px);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
z-index: 1001;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast.show {
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast i {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive Design */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.container {
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-content {
|
||||||
|
padding: 30px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo h1 {
|
||||||
|
font-size: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo i {
|
||||||
|
font-size: 36px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.results-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-actions {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast {
|
||||||
|
bottom: 20px;
|
||||||
|
right: 20px;
|
||||||
|
left: 20px;
|
||||||
|
transform: translateY(100px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast.show {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.input-card,
|
||||||
|
.result-card {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header h2,
|
||||||
|
.card-header h3 {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-value {
|
||||||
|
font-size: 12px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Animation Classes */
|
||||||
|
.fade-in {
|
||||||
|
animation: fadeIn 0.5s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(20px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-in {
|
||||||
|
animation: slideIn 0.3s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideIn {
|
||||||
|
from {
|
||||||
|
transform: translateX(-20px);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: translateX(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Custom Scrollbar */
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba(255, 255, 255, 0.3);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.5);
|
||||||
|
}
|
||||||
221
frontend/60sapi/实用功能/哈希解压压缩/index.html
Normal file
221
frontend/60sapi/实用功能/哈希解压压缩/index.html
Normal file
@@ -0,0 +1,221 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>多功能哈希工具 - Hash Toolkit</title>
|
||||||
|
<link rel="stylesheet" href="css/style.css">
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<!-- Header Section -->
|
||||||
|
<header class="header">
|
||||||
|
<div class="header-content">
|
||||||
|
<div class="logo">
|
||||||
|
<i class="fas fa-fingerprint"></i>
|
||||||
|
<h1>Hash Toolkit</h1>
|
||||||
|
</div>
|
||||||
|
<p class="subtitle">多功能哈希、编码与压缩工具</p>
|
||||||
|
</div>
|
||||||
|
<div class="header-decoration">
|
||||||
|
<div class="floating-shapes">
|
||||||
|
<div class="shape shape-1"></div>
|
||||||
|
<div class="shape shape-2"></div>
|
||||||
|
<div class="shape shape-3"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<!-- Main Content -->
|
||||||
|
<main class="main-content">
|
||||||
|
<!-- Input Section -->
|
||||||
|
<section class="input-section">
|
||||||
|
<div class="input-card">
|
||||||
|
<div class="card-header">
|
||||||
|
<i class="fas fa-edit"></i>
|
||||||
|
<h2>输入内容</h2>
|
||||||
|
</div>
|
||||||
|
<div class="input-wrapper">
|
||||||
|
<textarea
|
||||||
|
id="inputText"
|
||||||
|
placeholder="请输入要处理的文本内容...\n支持中文、英文、特殊字符等"
|
||||||
|
rows="6"
|
||||||
|
></textarea>
|
||||||
|
<div class="input-actions">
|
||||||
|
<button id="clearBtn" class="btn btn-secondary">
|
||||||
|
<i class="fas fa-trash"></i>
|
||||||
|
清空
|
||||||
|
</button>
|
||||||
|
<button id="processBtn" class="btn btn-primary">
|
||||||
|
<i class="fas fa-cogs"></i>
|
||||||
|
开始处理
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Results Section -->
|
||||||
|
<section class="results-section" id="resultsSection">
|
||||||
|
<div class="results-grid">
|
||||||
|
<!-- Hash Results -->
|
||||||
|
<div class="result-card hash-card">
|
||||||
|
<div class="card-header">
|
||||||
|
<i class="fas fa-hashtag"></i>
|
||||||
|
<h3>哈希算法</h3>
|
||||||
|
</div>
|
||||||
|
<div class="result-items">
|
||||||
|
<div class="result-item">
|
||||||
|
<label>MD5</label>
|
||||||
|
<div class="result-value" id="md5Result">
|
||||||
|
<span class="placeholder">等待处理...</span>
|
||||||
|
<button class="copy-btn" data-target="md5Result">
|
||||||
|
<i class="fas fa-copy"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="result-item">
|
||||||
|
<label>SHA1</label>
|
||||||
|
<div class="result-value" id="sha1Result">
|
||||||
|
<span class="placeholder">等待处理...</span>
|
||||||
|
<button class="copy-btn" data-target="sha1Result">
|
||||||
|
<i class="fas fa-copy"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="result-item">
|
||||||
|
<label>SHA256</label>
|
||||||
|
<div class="result-value" id="sha256Result">
|
||||||
|
<span class="placeholder">等待处理...</span>
|
||||||
|
<button class="copy-btn" data-target="sha256Result">
|
||||||
|
<i class="fas fa-copy"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="result-item">
|
||||||
|
<label>SHA512</label>
|
||||||
|
<div class="result-value" id="sha512Result">
|
||||||
|
<span class="placeholder">等待处理...</span>
|
||||||
|
<button class="copy-btn" data-target="sha512Result">
|
||||||
|
<i class="fas fa-copy"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Encoding Results -->
|
||||||
|
<div class="result-card encoding-card">
|
||||||
|
<div class="card-header">
|
||||||
|
<i class="fas fa-code"></i>
|
||||||
|
<h3>编码转换</h3>
|
||||||
|
</div>
|
||||||
|
<div class="result-items">
|
||||||
|
<div class="result-item">
|
||||||
|
<label>Base64 编码</label>
|
||||||
|
<div class="result-value" id="base64EncodeResult">
|
||||||
|
<span class="placeholder">等待处理...</span>
|
||||||
|
<button class="copy-btn" data-target="base64EncodeResult">
|
||||||
|
<i class="fas fa-copy"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="result-item">
|
||||||
|
<label>Base64 解码</label>
|
||||||
|
<div class="result-value" id="base64DecodeResult">
|
||||||
|
<span class="placeholder">等待处理...</span>
|
||||||
|
<button class="copy-btn" data-target="base64DecodeResult">
|
||||||
|
<i class="fas fa-copy"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="result-item">
|
||||||
|
<label>URL 编码</label>
|
||||||
|
<div class="result-value" id="urlEncodeResult">
|
||||||
|
<span class="placeholder">等待处理...</span>
|
||||||
|
<button class="copy-btn" data-target="urlEncodeResult">
|
||||||
|
<i class="fas fa-copy"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="result-item">
|
||||||
|
<label>URL 解码</label>
|
||||||
|
<div class="result-value" id="urlDecodeResult">
|
||||||
|
<span class="placeholder">等待处理...</span>
|
||||||
|
<button class="copy-btn" data-target="urlDecodeResult">
|
||||||
|
<i class="fas fa-copy"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Compression Results -->
|
||||||
|
<div class="result-card compression-card">
|
||||||
|
<div class="card-header">
|
||||||
|
<i class="fas fa-compress-alt"></i>
|
||||||
|
<h3>压缩算法</h3>
|
||||||
|
</div>
|
||||||
|
<div class="result-items">
|
||||||
|
<div class="result-item">
|
||||||
|
<label>Gzip 压缩</label>
|
||||||
|
<div class="result-value" id="gzipCompressResult">
|
||||||
|
<span class="placeholder">等待处理...</span>
|
||||||
|
<button class="copy-btn" data-target="gzipCompressResult">
|
||||||
|
<i class="fas fa-copy"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="result-item">
|
||||||
|
<label>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">
|
||||||
|
<span class="placeholder">等待处理...</span>
|
||||||
|
<button class="copy-btn" data-target="deflateCompressResult">
|
||||||
|
<i class="fas fa-copy"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="result-item">
|
||||||
|
<label>Brotli 压缩</label>
|
||||||
|
<div class="result-value" id="brotliCompressResult">
|
||||||
|
<span class="placeholder">等待处理...</span>
|
||||||
|
<button class="copy-btn" data-target="brotliCompressResult">
|
||||||
|
<i class="fas fa-copy"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<!-- Loading Overlay -->
|
||||||
|
<div class="loading-overlay" id="loadingOverlay">
|
||||||
|
<div class="loading-spinner">
|
||||||
|
<div class="spinner"></div>
|
||||||
|
<p>正在处理中...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Toast Notification -->
|
||||||
|
<div class="toast" id="toast">
|
||||||
|
<i class="fas fa-check-circle"></i>
|
||||||
|
<span id="toastMessage">复制成功!</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="js/script.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
380
frontend/60sapi/实用功能/哈希解压压缩/js/script.js
Normal file
380
frontend/60sapi/实用功能/哈希解压压缩/js/script.js
Normal file
@@ -0,0 +1,380 @@
|
|||||||
|
// API配置
|
||||||
|
const API_BASE_URL = 'https://60s.viki.moe/v2/hash';
|
||||||
|
|
||||||
|
// DOM元素
|
||||||
|
const elements = {
|
||||||
|
inputText: document.getElementById('inputText'),
|
||||||
|
processBtn: document.getElementById('processBtn'),
|
||||||
|
clearBtn: document.getElementById('clearBtn'),
|
||||||
|
resultsSection: document.getElementById('resultsSection'),
|
||||||
|
loadingOverlay: document.getElementById('loadingOverlay'),
|
||||||
|
toast: document.getElementById('toast'),
|
||||||
|
toastMessage: document.getElementById('toastMessage')
|
||||||
|
};
|
||||||
|
|
||||||
|
// 结果元素映射
|
||||||
|
const resultElements = {
|
||||||
|
md5: document.getElementById('md5Result'),
|
||||||
|
sha1: document.getElementById('sha1Result'),
|
||||||
|
sha256: document.getElementById('sha256Result'),
|
||||||
|
sha512: document.getElementById('sha512Result'),
|
||||||
|
base64Encode: document.getElementById('base64EncodeResult'),
|
||||||
|
base64Decode: document.getElementById('base64DecodeResult'),
|
||||||
|
urlEncode: document.getElementById('urlEncodeResult'),
|
||||||
|
urlDecode: document.getElementById('urlDecodeResult'),
|
||||||
|
gzipCompress: document.getElementById('gzipCompressResult'),
|
||||||
|
gzipDecompress: document.getElementById('gzipDecompressResult'),
|
||||||
|
deflateCompress: document.getElementById('deflateCompressResult'),
|
||||||
|
brotliCompress: document.getElementById('brotliCompressResult')
|
||||||
|
};
|
||||||
|
|
||||||
|
// 初始化
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
initializeEventListeners();
|
||||||
|
addInputAnimation();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 事件监听器初始化
|
||||||
|
function initializeEventListeners() {
|
||||||
|
// 处理按钮点击
|
||||||
|
elements.processBtn.addEventListener('click', handleProcess);
|
||||||
|
|
||||||
|
// 清空按钮点击
|
||||||
|
elements.clearBtn.addEventListener('click', handleClear);
|
||||||
|
|
||||||
|
// 输入框回车键
|
||||||
|
elements.inputText.addEventListener('keydown', function(e) {
|
||||||
|
if (e.ctrlKey && e.key === 'Enter') {
|
||||||
|
handleProcess();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 复制按钮事件委托
|
||||||
|
document.addEventListener('click', function(e) {
|
||||||
|
if (e.target.closest('.copy-btn')) {
|
||||||
|
const copyBtn = e.target.closest('.copy-btn');
|
||||||
|
const targetId = copyBtn.getAttribute('data-target');
|
||||||
|
const targetElement = document.getElementById(targetId);
|
||||||
|
const textContent = targetElement.textContent.trim();
|
||||||
|
|
||||||
|
if (textContent && textContent !== '等待处理...' && textContent !== '处理失败') {
|
||||||
|
copyToClipboard(textContent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 输入框实时验证
|
||||||
|
elements.inputText.addEventListener('input', function() {
|
||||||
|
const hasContent = this.value.trim().length > 0;
|
||||||
|
elements.processBtn.disabled = !hasContent;
|
||||||
|
|
||||||
|
if (hasContent) {
|
||||||
|
elements.processBtn.classList.remove('disabled');
|
||||||
|
} else {
|
||||||
|
elements.processBtn.classList.add('disabled');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加输入动画效果
|
||||||
|
function addInputAnimation() {
|
||||||
|
elements.inputText.addEventListener('focus', function() {
|
||||||
|
this.parentElement.classList.add('focused');
|
||||||
|
});
|
||||||
|
|
||||||
|
elements.inputText.addEventListener('blur', function() {
|
||||||
|
this.parentElement.classList.remove('focused');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理主要功能
|
||||||
|
async function handleProcess() {
|
||||||
|
const inputValue = elements.inputText.value.trim();
|
||||||
|
|
||||||
|
if (!inputValue) {
|
||||||
|
showToast('请输入要处理的内容', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示加载状态
|
||||||
|
showLoading(true);
|
||||||
|
resetResults();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 调用API
|
||||||
|
const response = await fetch(`${API_BASE_URL}?content=${encodeURIComponent(inputValue)}`);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.code === 200 && data.data) {
|
||||||
|
displayResults(data.data);
|
||||||
|
showResultsSection();
|
||||||
|
showToast('处理完成!', 'success');
|
||||||
|
} else {
|
||||||
|
throw new Error(data.message || '处理失败');
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('处理错误:', error);
|
||||||
|
showToast(`处理失败: ${error.message}`, 'error');
|
||||||
|
displayError();
|
||||||
|
} finally {
|
||||||
|
showLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示结果
|
||||||
|
function displayResults(data) {
|
||||||
|
try {
|
||||||
|
// 哈希结果
|
||||||
|
updateResultElement('md5', data.md5 || '不可用');
|
||||||
|
|
||||||
|
// SHA系列
|
||||||
|
if (data.sha) {
|
||||||
|
updateResultElement('sha1', data.sha.sha1 || '不可用');
|
||||||
|
updateResultElement('sha256', data.sha.sha256 || '不可用');
|
||||||
|
updateResultElement('sha512', data.sha.sha512 || '不可用');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Base64编码
|
||||||
|
if (data.base64) {
|
||||||
|
updateResultElement('base64Encode', data.base64.encode || '不可用');
|
||||||
|
updateResultElement('base64Decode', data.base64.decode || '不可用');
|
||||||
|
}
|
||||||
|
|
||||||
|
// URL编码
|
||||||
|
if (data.url) {
|
||||||
|
updateResultElement('urlEncode', data.url.encode || '不可用');
|
||||||
|
updateResultElement('urlDecode', data.url.decode || '不可用');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 压缩结果
|
||||||
|
if (data.gzip) {
|
||||||
|
updateResultElement('gzipCompress', data.gzip.compress || '不可用');
|
||||||
|
updateResultElement('gzipDecompress', data.gzip.decompress || '不可用');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.deflate) {
|
||||||
|
updateResultElement('deflateCompress', data.deflate.compress || '不可用');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.brotli) {
|
||||||
|
updateResultElement('brotliCompress', data.brotli.compress || '不可用');
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('显示结果时出错:', error);
|
||||||
|
showToast('显示结果时出错', 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新单个结果元素
|
||||||
|
function updateResultElement(key, value) {
|
||||||
|
const element = resultElements[key];
|
||||||
|
if (element) {
|
||||||
|
const textSpan = element.querySelector('span') || element;
|
||||||
|
textSpan.textContent = value;
|
||||||
|
textSpan.classList.remove('placeholder');
|
||||||
|
|
||||||
|
// 添加动画效果
|
||||||
|
element.classList.add('slide-in');
|
||||||
|
setTimeout(() => {
|
||||||
|
element.classList.remove('slide-in');
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置结果
|
||||||
|
function resetResults() {
|
||||||
|
Object.values(resultElements).forEach(element => {
|
||||||
|
if (element) {
|
||||||
|
const textSpan = element.querySelector('span') || element;
|
||||||
|
textSpan.textContent = '等待处理...';
|
||||||
|
textSpan.classList.add('placeholder');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示错误状态
|
||||||
|
function displayError() {
|
||||||
|
Object.values(resultElements).forEach(element => {
|
||||||
|
if (element) {
|
||||||
|
const textSpan = element.querySelector('span') || element;
|
||||||
|
textSpan.textContent = '处理失败';
|
||||||
|
textSpan.classList.add('placeholder');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示结果区域
|
||||||
|
function showResultsSection() {
|
||||||
|
elements.resultsSection.classList.add('show');
|
||||||
|
|
||||||
|
// 平滑滚动到结果区域
|
||||||
|
setTimeout(() => {
|
||||||
|
elements.resultsSection.scrollIntoView({
|
||||||
|
behavior: 'smooth',
|
||||||
|
block: 'start'
|
||||||
|
});
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清空功能
|
||||||
|
function handleClear() {
|
||||||
|
elements.inputText.value = '';
|
||||||
|
elements.inputText.focus();
|
||||||
|
elements.resultsSection.classList.remove('show');
|
||||||
|
resetResults();
|
||||||
|
elements.processBtn.disabled = true;
|
||||||
|
elements.processBtn.classList.add('disabled');
|
||||||
|
|
||||||
|
showToast('内容已清空', 'info');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 复制到剪贴板
|
||||||
|
async function copyToClipboard(text) {
|
||||||
|
try {
|
||||||
|
if (navigator.clipboard && window.isSecureContext) {
|
||||||
|
await navigator.clipboard.writeText(text);
|
||||||
|
} else {
|
||||||
|
// 降级方案
|
||||||
|
const textArea = document.createElement('textarea');
|
||||||
|
textArea.value = text;
|
||||||
|
textArea.style.position = 'fixed';
|
||||||
|
textArea.style.left = '-999999px';
|
||||||
|
textArea.style.top = '-999999px';
|
||||||
|
document.body.appendChild(textArea);
|
||||||
|
textArea.focus();
|
||||||
|
textArea.select();
|
||||||
|
document.execCommand('copy');
|
||||||
|
textArea.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
showToast('复制成功!', 'success');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('复制失败:', error);
|
||||||
|
showToast('复制失败,请手动复制', 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示/隐藏加载状态
|
||||||
|
function showLoading(show) {
|
||||||
|
if (show) {
|
||||||
|
elements.loadingOverlay.classList.add('show');
|
||||||
|
elements.processBtn.disabled = true;
|
||||||
|
elements.processBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> 处理中...';
|
||||||
|
} else {
|
||||||
|
elements.loadingOverlay.classList.remove('show');
|
||||||
|
elements.processBtn.disabled = false;
|
||||||
|
elements.processBtn.innerHTML = '<i class="fas fa-cogs"></i> 开始处理';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示提示消息
|
||||||
|
function showToast(message, type = 'success') {
|
||||||
|
elements.toastMessage.textContent = message;
|
||||||
|
|
||||||
|
// 设置图标和样式
|
||||||
|
const icon = elements.toast.querySelector('i');
|
||||||
|
icon.className = getToastIcon(type);
|
||||||
|
|
||||||
|
elements.toast.className = `toast ${type}`;
|
||||||
|
elements.toast.classList.add('show');
|
||||||
|
|
||||||
|
// 自动隐藏
|
||||||
|
setTimeout(() => {
|
||||||
|
elements.toast.classList.remove('show');
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取提示图标
|
||||||
|
function getToastIcon(type) {
|
||||||
|
const icons = {
|
||||||
|
success: 'fas fa-check-circle',
|
||||||
|
error: 'fas fa-exclamation-circle',
|
||||||
|
info: 'fas fa-info-circle',
|
||||||
|
warning: 'fas fa-exclamation-triangle'
|
||||||
|
};
|
||||||
|
return icons[type] || icons.success;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 工具函数:防抖
|
||||||
|
function debounce(func, wait) {
|
||||||
|
let timeout;
|
||||||
|
return function executedFunction(...args) {
|
||||||
|
const later = () => {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
func(...args);
|
||||||
|
};
|
||||||
|
clearTimeout(timeout);
|
||||||
|
timeout = setTimeout(later, wait);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 工具函数:节流
|
||||||
|
function throttle(func, limit) {
|
||||||
|
let inThrottle;
|
||||||
|
return function() {
|
||||||
|
const args = arguments;
|
||||||
|
const context = this;
|
||||||
|
if (!inThrottle) {
|
||||||
|
func.apply(context, args);
|
||||||
|
inThrottle = true;
|
||||||
|
setTimeout(() => inThrottle = false, limit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加键盘快捷键支持
|
||||||
|
document.addEventListener('keydown', function(e) {
|
||||||
|
// Ctrl+Enter 处理
|
||||||
|
if (e.ctrlKey && e.key === 'Enter') {
|
||||||
|
e.preventDefault();
|
||||||
|
if (!elements.processBtn.disabled) {
|
||||||
|
handleProcess();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Escape 清空
|
||||||
|
if (e.key === 'Escape') {
|
||||||
|
handleClear();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 页面可见性变化处理
|
||||||
|
document.addEventListener('visibilitychange', function() {
|
||||||
|
if (document.hidden) {
|
||||||
|
// 页面隐藏时的处理
|
||||||
|
console.log('页面已隐藏');
|
||||||
|
} else {
|
||||||
|
// 页面显示时的处理
|
||||||
|
console.log('页面已显示');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 错误处理
|
||||||
|
window.addEventListener('error', function(e) {
|
||||||
|
console.error('全局错误:', e.error);
|
||||||
|
showToast('发生未知错误,请刷新页面重试', 'error');
|
||||||
|
});
|
||||||
|
|
||||||
|
// 未处理的Promise拒绝
|
||||||
|
window.addEventListener('unhandledrejection', function(e) {
|
||||||
|
console.error('未处理的Promise拒绝:', e.reason);
|
||||||
|
showToast('网络请求失败,请检查网络连接', 'error');
|
||||||
|
});
|
||||||
|
|
||||||
|
// 导出函数供测试使用
|
||||||
|
if (typeof module !== 'undefined' && module.exports) {
|
||||||
|
module.exports = {
|
||||||
|
handleProcess,
|
||||||
|
copyToClipboard,
|
||||||
|
showToast,
|
||||||
|
debounce,
|
||||||
|
throttle
|
||||||
|
};
|
||||||
|
}
|
||||||
3
frontend/60sapi/实用功能/哈希解压压缩/接口集合.json
Normal file
3
frontend/60sapi/实用功能/哈希解压压缩/接口集合.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
[
|
||||||
|
"https://60s.api.shumengya.top"
|
||||||
|
]
|
||||||
33
frontend/60sapi/实用功能/哈希解压压缩/返回接口.json
Normal file
33
frontend/60sapi/实用功能/哈希解压压缩/返回接口.json
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "处理成功",
|
||||||
|
"data": {
|
||||||
|
"source": "你好👋",
|
||||||
|
"md5": "a1b2c3d4e5f6789012345678901234567",
|
||||||
|
"sha": {
|
||||||
|
"sha1": "da39a3ee5e6b4b0d3255bfef95601890afd80709",
|
||||||
|
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||||
|
"sha512": "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e"
|
||||||
|
},
|
||||||
|
"base64": {
|
||||||
|
"encode": "5L2g5aW9",
|
||||||
|
"decode": "你好"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"encode": "%E4%BD%A0%E5%A5%BD%F0%9F%91%8B",
|
||||||
|
"decode": "你好👋"
|
||||||
|
},
|
||||||
|
"gzip": {
|
||||||
|
"compress": "H4sIAAAAAAAAA...(压缩后的数据)",
|
||||||
|
"decompress": "你好👋"
|
||||||
|
},
|
||||||
|
"deflate": {
|
||||||
|
"compress": "eJwrz8kvTUlMy...(压缩后的数据)",
|
||||||
|
"decompress": "你好👋"
|
||||||
|
},
|
||||||
|
"brotli": {
|
||||||
|
"compress": "CwWAaGVsbG8g...(压缩后的数据)",
|
||||||
|
"decompress": "你好👋"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,4 +5,4 @@
|
|||||||
5.返回接口.json储存了网页api返回的数据格式
|
5.返回接口.json储存了网页api返回的数据格式
|
||||||
6.严格按照用户要求执行,不得随意添加什么注解,如“以下数据来自...”
|
6.严格按照用户要求执行,不得随意添加什么注解,如“以下数据来自...”
|
||||||
7.接口集合.json保存了所有已知的后端API接口,一个访问不了尝试自动切换另一个
|
7.接口集合.json保存了所有已知的后端API接口,一个访问不了尝试自动切换另一个
|
||||||
8.在css中有关背景的css单独一个css文件,方便我直接迁移
|
8.在css中有关背景的css单独一个css文件,方便我直接迁移
|
||||||
232
frontend/60sapi/实用功能/链接OG信息/css/background.css
Normal file
232
frontend/60sapi/实用功能/链接OG信息/css/background.css
Normal file
@@ -0,0 +1,232 @@
|
|||||||
|
/* 高维度背景特效样式 - 神秘高级风格 */
|
||||||
|
|
||||||
|
/* 背景容器 */
|
||||||
|
.background-container {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
pointer-events: none;
|
||||||
|
background: radial-gradient(ellipse at center,
|
||||||
|
rgba(15, 0, 30, 0.95) 0%,
|
||||||
|
rgba(5, 0, 15, 0.98) 50%,
|
||||||
|
rgba(0, 0, 0, 1) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 几何网格层 */
|
||||||
|
.geometric-grid {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-image:
|
||||||
|
linear-gradient(rgba(138, 43, 226, 0.1) 1px, transparent 1px),
|
||||||
|
linear-gradient(90deg, rgba(138, 43, 226, 0.1) 1px, transparent 1px),
|
||||||
|
linear-gradient(rgba(75, 0, 130, 0.05) 1px, transparent 1px),
|
||||||
|
linear-gradient(90deg, rgba(75, 0, 130, 0.05) 1px, transparent 1px);
|
||||||
|
background-size: 100px 100px, 100px 100px, 20px 20px, 20px 20px;
|
||||||
|
animation: gridPulse 8s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes gridPulse {
|
||||||
|
0%, 100% { opacity: 0.3; transform: scale(1); }
|
||||||
|
50% { opacity: 0.6; transform: scale(1.02); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 神经网络效果 */
|
||||||
|
.neural-network {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background:
|
||||||
|
radial-gradient(circle at 20% 30%, rgba(138, 43, 226, 0.15) 2px, transparent 2px),
|
||||||
|
radial-gradient(circle at 80% 20%, rgba(75, 0, 130, 0.12) 1px, transparent 1px),
|
||||||
|
radial-gradient(circle at 40% 70%, rgba(147, 0, 211, 0.1) 1.5px, transparent 1.5px),
|
||||||
|
radial-gradient(circle at 90% 80%, rgba(138, 43, 226, 0.08) 1px, transparent 1px),
|
||||||
|
radial-gradient(circle at 10% 90%, rgba(75, 0, 130, 0.1) 2px, transparent 2px);
|
||||||
|
background-size: 200px 200px, 150px 150px, 300px 300px, 180px 180px, 250px 250px;
|
||||||
|
animation: neuralFlow 15s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes neuralFlow {
|
||||||
|
0% { transform: translate(0, 0) rotate(0deg); }
|
||||||
|
25% { transform: translate(-10px, -5px) rotate(90deg); }
|
||||||
|
50% { transform: translate(-5px, -10px) rotate(180deg); }
|
||||||
|
75% { transform: translate(5px, -5px) rotate(270deg); }
|
||||||
|
100% { transform: translate(0, 0) rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 粒子系统 */
|
||||||
|
.particle-system {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-image:
|
||||||
|
radial-gradient(circle, rgba(138, 43, 226, 0.4) 1px, transparent 1px),
|
||||||
|
radial-gradient(circle, rgba(75, 0, 130, 0.3) 0.5px, transparent 0.5px),
|
||||||
|
radial-gradient(circle, rgba(147, 0, 211, 0.2) 0.8px, transparent 0.8px);
|
||||||
|
background-size: 80px 80px, 120px 120px, 160px 160px;
|
||||||
|
background-position: 0 0, 40px 40px, 80px 80px;
|
||||||
|
animation: particleFloat 20s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes particleFloat {
|
||||||
|
0%, 100% { transform: translateY(0px) translateX(0px); }
|
||||||
|
25% { transform: translateY(-20px) translateX(10px); }
|
||||||
|
50% { transform: translateY(-10px) translateX(-15px); }
|
||||||
|
75% { transform: translateY(-30px) translateX(5px); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 扫描线效果 */
|
||||||
|
.scan-lines {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: repeating-linear-gradient(
|
||||||
|
0deg,
|
||||||
|
transparent 0px,
|
||||||
|
transparent 2px,
|
||||||
|
rgba(138, 43, 226, 0.03) 2px,
|
||||||
|
rgba(138, 43, 226, 0.03) 4px
|
||||||
|
);
|
||||||
|
animation: scanMove 3s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes scanMove {
|
||||||
|
0% { transform: translateY(-100%); }
|
||||||
|
100% { transform: translateY(100%); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 全息投影效果 */
|
||||||
|
.holographic-overlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background:
|
||||||
|
linear-gradient(45deg,
|
||||||
|
transparent 30%,
|
||||||
|
rgba(138, 43, 226, 0.05) 50%,
|
||||||
|
transparent 70%),
|
||||||
|
linear-gradient(-45deg,
|
||||||
|
transparent 30%,
|
||||||
|
rgba(75, 0, 130, 0.03) 50%,
|
||||||
|
transparent 70%);
|
||||||
|
background-size: 200px 200px, 150px 150px;
|
||||||
|
animation: holographicShift 12s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes holographicShift {
|
||||||
|
0%, 100% {
|
||||||
|
background-position: 0% 0%, 100% 100%;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
background-position: 100% 100%, 0% 0%;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 数据流效果 */
|
||||||
|
.data-stream {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background:
|
||||||
|
linear-gradient(90deg,
|
||||||
|
transparent 0%,
|
||||||
|
rgba(138, 43, 226, 0.1) 50%,
|
||||||
|
transparent 100%);
|
||||||
|
background-size: 300px 100%;
|
||||||
|
animation: dataFlow 8s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes dataFlow {
|
||||||
|
0% { transform: translateX(-100%); }
|
||||||
|
100% { transform: translateX(100%); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 量子波动效果 */
|
||||||
|
.quantum-waves {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background:
|
||||||
|
radial-gradient(ellipse 200px 100px at 50% 0%,
|
||||||
|
rgba(138, 43, 226, 0.1) 0%,
|
||||||
|
transparent 50%),
|
||||||
|
radial-gradient(ellipse 300px 150px at 50% 100%,
|
||||||
|
rgba(75, 0, 130, 0.08) 0%,
|
||||||
|
transparent 50%);
|
||||||
|
animation: quantumPulse 10s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes quantumPulse {
|
||||||
|
0%, 100% {
|
||||||
|
transform: scale(1) rotate(0deg);
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: scale(1.1) rotate(180deg);
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 响应式优化 */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.geometric-grid {
|
||||||
|
background-size: 50px 50px, 50px 50px, 10px 10px, 10px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.neural-network {
|
||||||
|
background-size: 100px 100px, 75px 75px, 150px 150px, 90px 90px, 125px 125px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.particle-system {
|
||||||
|
background-size: 40px 40px, 60px 60px, 80px 80px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 减少动画偏好 */
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.geometric-grid,
|
||||||
|
.neural-network,
|
||||||
|
.particle-system,
|
||||||
|
.scan-lines,
|
||||||
|
.holographic-overlay,
|
||||||
|
.data-stream,
|
||||||
|
.quantum-waves {
|
||||||
|
animation: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 高对比度模式 */
|
||||||
|
@media (prefers-contrast: high) {
|
||||||
|
.background-container {
|
||||||
|
background: radial-gradient(ellipse at center,
|
||||||
|
rgba(25, 0, 50, 0.95) 0%,
|
||||||
|
rgba(10, 0, 25, 0.98) 50%,
|
||||||
|
rgba(0, 0, 0, 1) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.geometric-grid {
|
||||||
|
background-image:
|
||||||
|
linear-gradient(rgba(200, 100, 255, 0.2) 1px, transparent 1px),
|
||||||
|
linear-gradient(90deg, rgba(200, 100, 255, 0.2) 1px, transparent 1px);
|
||||||
|
}
|
||||||
|
}
|
||||||
1159
frontend/60sapi/实用功能/链接OG信息/css/style.css
Normal file
1159
frontend/60sapi/实用功能/链接OG信息/css/style.css
Normal file
File diff suppressed because it is too large
Load Diff
223
frontend/60sapi/实用功能/链接OG信息/index.html
Normal file
223
frontend/60sapi/实用功能/链接OG信息/index.html
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>链接OG信息查询 - 神秘解析器</title>
|
||||||
|
<meta name="description" content="高级链接OG信息查询工具,解析网页元数据">
|
||||||
|
<link rel="stylesheet" href="css/style.css">
|
||||||
|
<link rel="stylesheet" href="css/background.css">
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<!-- 背景特效容器 -->
|
||||||
|
<div class="background-container">
|
||||||
|
<div class="matrix-rain"></div>
|
||||||
|
<div class="geometric-shapes"></div>
|
||||||
|
<div class="neural-network"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 主容器 -->
|
||||||
|
<div class="main-container">
|
||||||
|
<!-- 头部区域 -->
|
||||||
|
<header class="header">
|
||||||
|
<div class="header-content">
|
||||||
|
<div class="logo-section">
|
||||||
|
<i class="fas fa-link logo-icon"></i>
|
||||||
|
<h1 class="title">OG 解析器</h1>
|
||||||
|
<span class="subtitle">链接元数据神秘解析</span>
|
||||||
|
</div>
|
||||||
|
<div class="status-indicator">
|
||||||
|
<div class="pulse-dot"></div>
|
||||||
|
<span class="status-text">系统就绪</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<!-- 查询区域 -->
|
||||||
|
<section class="query-section">
|
||||||
|
<div class="input-container">
|
||||||
|
<div class="input-wrapper">
|
||||||
|
<i class="fas fa-globe input-icon"></i>
|
||||||
|
<input type="url" id="url-input" placeholder="输入链接地址进行深度解析..." class="url-input">
|
||||||
|
<div class="input-border"></div>
|
||||||
|
</div>
|
||||||
|
<button id="analyze-btn" class="analyze-btn">
|
||||||
|
<span class="btn-text">开始解析</span>
|
||||||
|
<div class="btn-effects">
|
||||||
|
<div class="ripple"></div>
|
||||||
|
<div class="glow"></div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- 加载状态 -->
|
||||||
|
<div id="loading" class="loading-container" style="display: none;">
|
||||||
|
<div class="loading-content">
|
||||||
|
<div class="scanner">
|
||||||
|
<div class="scanner-line"></div>
|
||||||
|
<div class="scanner-grid">
|
||||||
|
<div class="grid-line"></div>
|
||||||
|
<div class="grid-line"></div>
|
||||||
|
<div class="grid-line"></div>
|
||||||
|
<div class="grid-line"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="loading-text">
|
||||||
|
<span class="loading-title">正在解析链接</span>
|
||||||
|
<span class="loading-subtitle">深度扫描元数据中...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 结果展示区域 -->
|
||||||
|
<section id="results" class="results-section" style="display: none;">
|
||||||
|
<div class="results-header">
|
||||||
|
<h2 class="results-title">
|
||||||
|
<i class="fas fa-chart-network"></i>
|
||||||
|
解析结果
|
||||||
|
</h2>
|
||||||
|
<div class="results-actions">
|
||||||
|
<button id="copy-btn" class="action-btn">
|
||||||
|
<i class="fas fa-copy"></i>
|
||||||
|
<span>复制数据</span>
|
||||||
|
</button>
|
||||||
|
<button id="clear-btn" class="action-btn">
|
||||||
|
<i class="fas fa-trash"></i>
|
||||||
|
<span>清除结果</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="og-card">
|
||||||
|
<!-- 基础信息 -->
|
||||||
|
<div class="info-section basic-info">
|
||||||
|
<div class="section-header">
|
||||||
|
<i class="fas fa-info-circle"></i>
|
||||||
|
<span>基础信息</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-grid">
|
||||||
|
<div class="info-item">
|
||||||
|
<label>标题</label>
|
||||||
|
<div id="og-title" class="info-value">-</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<label>描述</label>
|
||||||
|
<div id="og-description" class="info-value">-</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<label>网站名称</label>
|
||||||
|
<div id="og-site-name" class="info-value">-</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<label>类型</label>
|
||||||
|
<div id="og-type" class="info-value">-</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 媒体信息 -->
|
||||||
|
<div class="info-section media-info">
|
||||||
|
<div class="section-header">
|
||||||
|
<i class="fas fa-image"></i>
|
||||||
|
<span>媒体信息</span>
|
||||||
|
</div>
|
||||||
|
<div class="media-preview" id="media-preview">
|
||||||
|
<div class="no-media">
|
||||||
|
<i class="fas fa-image-slash"></i>
|
||||||
|
<span>暂无媒体内容</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="media-details">
|
||||||
|
<div class="info-item">
|
||||||
|
<label>图片URL</label>
|
||||||
|
<div id="og-image" class="info-value url-value">-</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<label>图片尺寸</label>
|
||||||
|
<div id="og-image-size" class="info-value">-</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 技术信息 -->
|
||||||
|
<div class="info-section tech-info">
|
||||||
|
<div class="section-header">
|
||||||
|
<i class="fas fa-code"></i>
|
||||||
|
<span>技术信息</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-grid">
|
||||||
|
<div class="info-item">
|
||||||
|
<label>URL</label>
|
||||||
|
<div id="og-url" class="info-value url-value">-</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<label>域名</label>
|
||||||
|
<div id="og-domain" class="info-value">-</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<label>语言</label>
|
||||||
|
<div id="og-locale" class="info-value">-</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<label>字符编码</label>
|
||||||
|
<div id="og-charset" class="info-value">-</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- 错误信息 -->
|
||||||
|
<div id="error" class="error-container" style="display: none;">
|
||||||
|
<div class="error-content">
|
||||||
|
<div class="error-icon">
|
||||||
|
<i class="fas fa-exclamation-triangle"></i>
|
||||||
|
</div>
|
||||||
|
<div class="error-text">
|
||||||
|
<h3 class="error-title">解析失败</h3>
|
||||||
|
<p id="error-message" class="error-message">未知错误</p>
|
||||||
|
</div>
|
||||||
|
<button id="retryBtn" class="retry-btn">
|
||||||
|
<i class="fas fa-redo"></i>
|
||||||
|
<span>重新尝试</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 提示消息 -->
|
||||||
|
<div id="tip-message" class="tip-container">
|
||||||
|
<div class="tip-content">
|
||||||
|
<i class="fas fa-lightbulb tip-icon"></i>
|
||||||
|
<span class="tip-text"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Toast消息 -->
|
||||||
|
<div id="toast" class="toast-container">
|
||||||
|
<div class="toast-content">
|
||||||
|
<i class="toast-icon"></i>
|
||||||
|
<span class="toast-message"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 页脚 -->
|
||||||
|
<footer class="footer">
|
||||||
|
<div class="footer-content">
|
||||||
|
<p class="footer-text">
|
||||||
|
<i class="fas fa-shield-alt"></i>
|
||||||
|
高级链接解析系统 | 神秘数据挖掘
|
||||||
|
</p>
|
||||||
|
<div class="footer-links">
|
||||||
|
<span class="footer-link">隐私保护</span>
|
||||||
|
<span class="footer-divider">|</span>
|
||||||
|
<span class="footer-link">安全解析</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<script src="js/script.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
555
frontend/60sapi/实用功能/链接OG信息/js/script.js
Normal file
555
frontend/60sapi/实用功能/链接OG信息/js/script.js
Normal file
@@ -0,0 +1,555 @@
|
|||||||
|
// 链接OG信息查询 - JavaScript功能代码
|
||||||
|
// 神秘高级风格的交互体验
|
||||||
|
|
||||||
|
class OGAnalyzer {
|
||||||
|
constructor() {
|
||||||
|
this.apiUrl = 'https://60s.viki.moe/v2/og';
|
||||||
|
this.isAnalyzing = false;
|
||||||
|
this.currentUrl = '';
|
||||||
|
this.animationFrameId = null;
|
||||||
|
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this.bindEvents();
|
||||||
|
this.createBackgroundEffects();
|
||||||
|
this.initializeAnimations();
|
||||||
|
this.showWelcomeMessage();
|
||||||
|
this.initPageAnimations();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化页面动画
|
||||||
|
initPageAnimations() {
|
||||||
|
// 延迟添加动画类,确保CSS已加载
|
||||||
|
setTimeout(() => {
|
||||||
|
const header = document.querySelector('.header');
|
||||||
|
const querySection = document.querySelector('.query-section');
|
||||||
|
|
||||||
|
if (header) header.classList.add('animate-in');
|
||||||
|
if (querySection) querySection.classList.add('animate-in');
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
bindEvents() {
|
||||||
|
const urlInput = document.getElementById('url-input');
|
||||||
|
const analyzeBtn = document.getElementById('analyze-btn');
|
||||||
|
const copyBtn = document.getElementById('copy-btn');
|
||||||
|
const clearBtn = document.getElementById('clear-btn');
|
||||||
|
|
||||||
|
// 输入框事件
|
||||||
|
urlInput.addEventListener('input', (e) => this.handleUrlInput(e));
|
||||||
|
urlInput.addEventListener('keypress', (e) => {
|
||||||
|
if (e.key === 'Enter' && !this.isAnalyzing) {
|
||||||
|
this.analyzeUrl();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
urlInput.addEventListener('focus', () => this.handleInputFocus());
|
||||||
|
urlInput.addEventListener('blur', () => this.handleInputBlur());
|
||||||
|
|
||||||
|
// 按钮事件
|
||||||
|
analyzeBtn.addEventListener('click', () => this.analyzeUrl());
|
||||||
|
copyBtn.addEventListener('click', () => this.copyResults());
|
||||||
|
clearBtn.addEventListener('click', () => this.clearResults());
|
||||||
|
|
||||||
|
// 键盘快捷键
|
||||||
|
document.addEventListener('keydown', (e) => this.handleKeyboard(e));
|
||||||
|
}
|
||||||
|
|
||||||
|
handleUrlInput(e) {
|
||||||
|
const url = e.target.value.trim();
|
||||||
|
const analyzeBtn = document.getElementById('analyze-btn');
|
||||||
|
|
||||||
|
if (this.isValidUrl(url)) {
|
||||||
|
analyzeBtn.classList.add('ready');
|
||||||
|
e.target.classList.remove('error');
|
||||||
|
} else {
|
||||||
|
analyzeBtn.classList.remove('ready');
|
||||||
|
if (url.length > 0) {
|
||||||
|
e.target.classList.add('error');
|
||||||
|
} else {
|
||||||
|
e.target.classList.remove('error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleInputFocus() {
|
||||||
|
const inputContainer = document.querySelector('.input-container');
|
||||||
|
inputContainer.classList.add('focused');
|
||||||
|
this.createInputGlow();
|
||||||
|
}
|
||||||
|
|
||||||
|
handleInputBlur() {
|
||||||
|
const inputContainer = document.querySelector('.input-container');
|
||||||
|
inputContainer.classList.remove('focused');
|
||||||
|
}
|
||||||
|
|
||||||
|
handleKeyboard(e) {
|
||||||
|
// Ctrl/Cmd + Enter 快速分析
|
||||||
|
if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
|
||||||
|
e.preventDefault();
|
||||||
|
if (!this.isAnalyzing) {
|
||||||
|
this.analyzeUrl();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Escape 清除结果
|
||||||
|
if (e.key === 'Escape') {
|
||||||
|
this.clearResults();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isValidUrl(string) {
|
||||||
|
try {
|
||||||
|
const url = new URL(string);
|
||||||
|
return url.protocol === 'http:' || url.protocol === 'https:';
|
||||||
|
} catch (_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async analyzeUrl() {
|
||||||
|
const urlInput = document.getElementById('url-input');
|
||||||
|
const url = urlInput.value.trim();
|
||||||
|
|
||||||
|
if (!this.isValidUrl(url)) {
|
||||||
|
this.showError('请输入有效的URL地址');
|
||||||
|
this.shakeInput();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isAnalyzing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.currentUrl = url;
|
||||||
|
this.isAnalyzing = true;
|
||||||
|
this.showLoading();
|
||||||
|
this.hideError();
|
||||||
|
this.hideResults();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${this.apiUrl}?url=${encodeURIComponent(url)}`);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.code === 200 && data.data) {
|
||||||
|
await this.displayResults(data.data);
|
||||||
|
this.showSuccessMessage('分析完成!');
|
||||||
|
|
||||||
|
// 添加按钮闪烁效果
|
||||||
|
const analyzeBtn = document.getElementById('analyze-btn');
|
||||||
|
analyzeBtn.classList.add('flash');
|
||||||
|
setTimeout(() => {
|
||||||
|
analyzeBtn.classList.remove('flash');
|
||||||
|
}, 300);
|
||||||
|
} else {
|
||||||
|
throw new Error(data.message || '获取OG信息失败');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('分析失败:', error);
|
||||||
|
this.showError(`分析失败: ${error.message}`);
|
||||||
|
} finally {
|
||||||
|
this.isAnalyzing = false;
|
||||||
|
this.hideLoading();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
showLoading() {
|
||||||
|
const loadingElement = document.getElementById('loading');
|
||||||
|
const analyzeBtn = document.getElementById('analyze-btn');
|
||||||
|
|
||||||
|
loadingElement.classList.add('active');
|
||||||
|
analyzeBtn.disabled = true;
|
||||||
|
analyzeBtn.textContent = '分析中...';
|
||||||
|
|
||||||
|
this.startScannerAnimation();
|
||||||
|
}
|
||||||
|
|
||||||
|
hideLoading() {
|
||||||
|
const loadingElement = document.getElementById('loading');
|
||||||
|
const analyzeBtn = document.getElementById('analyze-btn');
|
||||||
|
|
||||||
|
loadingElement.classList.remove('active');
|
||||||
|
analyzeBtn.disabled = false;
|
||||||
|
analyzeBtn.textContent = '开始分析';
|
||||||
|
|
||||||
|
this.stopScannerAnimation();
|
||||||
|
}
|
||||||
|
|
||||||
|
async displayResults(data) {
|
||||||
|
const resultsElement = document.getElementById('results');
|
||||||
|
const ogCard = document.getElementById('og-card');
|
||||||
|
|
||||||
|
// 基础信息
|
||||||
|
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');
|
||||||
|
|
||||||
|
// 媒体信息
|
||||||
|
this.updateImageElement('og-image', data.image);
|
||||||
|
this.updateElement('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`);
|
||||||
|
|
||||||
|
// 显示结果
|
||||||
|
resultsElement.classList.add('active');
|
||||||
|
|
||||||
|
// 添加动画效果
|
||||||
|
await this.animateResults();
|
||||||
|
|
||||||
|
// 启用操作按钮
|
||||||
|
document.getElementById('copy-btn').disabled = false;
|
||||||
|
document.getElementById('clear-btn').disabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateElement(id, content) {
|
||||||
|
const element = document.getElementById(id);
|
||||||
|
if (element) {
|
||||||
|
element.textContent = content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateImageElement(id, imageSrc) {
|
||||||
|
const element = document.getElementById(id);
|
||||||
|
if (element && imageSrc) {
|
||||||
|
element.src = imageSrc;
|
||||||
|
element.style.display = 'block';
|
||||||
|
element.onerror = () => {
|
||||||
|
element.style.display = 'none';
|
||||||
|
const placeholder = element.nextElementSibling;
|
||||||
|
if (placeholder && placeholder.classList.contains('image-placeholder')) {
|
||||||
|
placeholder.style.display = 'flex';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else if (element) {
|
||||||
|
element.style.display = 'none';
|
||||||
|
const placeholder = element.nextElementSibling;
|
||||||
|
if (placeholder && placeholder.classList.contains('image-placeholder')) {
|
||||||
|
placeholder.style.display = 'flex';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
formatDate(timestamp) {
|
||||||
|
if (!timestamp) return '未知';
|
||||||
|
try {
|
||||||
|
const date = new Date(timestamp);
|
||||||
|
return date.toLocaleString('zh-CN');
|
||||||
|
} catch (e) {
|
||||||
|
return '格式错误';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async animateResults() {
|
||||||
|
const cards = document.querySelectorAll('.info-card');
|
||||||
|
|
||||||
|
for (let i = 0; i < cards.length; i++) {
|
||||||
|
setTimeout(() => {
|
||||||
|
cards[i].classList.add('animate-in');
|
||||||
|
}, i * 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 等待动画完成
|
||||||
|
await new Promise(resolve => setTimeout(resolve, cards.length * 100 + 300));
|
||||||
|
}
|
||||||
|
|
||||||
|
showError(message) {
|
||||||
|
const errorElement = document.getElementById('error-message');
|
||||||
|
const errorText = errorElement.querySelector('.error-text');
|
||||||
|
const inputContainer = document.querySelector('.input-container');
|
||||||
|
|
||||||
|
errorText.textContent = message;
|
||||||
|
errorElement.classList.add('active');
|
||||||
|
|
||||||
|
// 添加震动效果
|
||||||
|
if (inputContainer) {
|
||||||
|
inputContainer.classList.add('shake');
|
||||||
|
setTimeout(() => {
|
||||||
|
inputContainer.classList.remove('shake');
|
||||||
|
}, 600);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 自动隐藏错误信息
|
||||||
|
setTimeout(() => {
|
||||||
|
this.hideError();
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
hideError() {
|
||||||
|
const errorElement = document.getElementById('error-message');
|
||||||
|
errorElement.classList.remove('active');
|
||||||
|
}
|
||||||
|
|
||||||
|
hideResults() {
|
||||||
|
const resultsElement = document.getElementById('results');
|
||||||
|
resultsElement.classList.remove('active');
|
||||||
|
|
||||||
|
// 重置动画状态
|
||||||
|
const cards = document.querySelectorAll('.info-card');
|
||||||
|
cards.forEach(card => card.classList.remove('animate-in'));
|
||||||
|
}
|
||||||
|
|
||||||
|
showSuccessMessage(message) {
|
||||||
|
const tipElement = document.getElementById('tip-message');
|
||||||
|
const tipText = tipElement.querySelector('.tip-text');
|
||||||
|
|
||||||
|
tipText.textContent = message;
|
||||||
|
tipElement.classList.add('active');
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
tipElement.classList.remove('active');
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
shakeInput() {
|
||||||
|
const inputContainer = document.querySelector('.input-container');
|
||||||
|
inputContainer.classList.add('shake');
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
inputContainer.classList.remove('shake');
|
||||||
|
}, 600);
|
||||||
|
}
|
||||||
|
|
||||||
|
copyResults() {
|
||||||
|
const ogData = {
|
||||||
|
title: document.getElementById('og-title').textContent,
|
||||||
|
description: document.getElementById('og-description').textContent,
|
||||||
|
url: document.getElementById('og-url').textContent,
|
||||||
|
site_name: document.getElementById('og-site-name').textContent,
|
||||||
|
type: document.getElementById('og-type').textContent,
|
||||||
|
image: document.getElementById('og-image').src,
|
||||||
|
locale: document.getElementById('og-locale').textContent
|
||||||
|
};
|
||||||
|
|
||||||
|
const jsonString = JSON.stringify(ogData, null, 2);
|
||||||
|
|
||||||
|
navigator.clipboard.writeText(jsonString).then(() => {
|
||||||
|
this.showSuccessMessage('结果已复制到剪贴板!');
|
||||||
|
this.flashCopyButton();
|
||||||
|
}).catch(err => {
|
||||||
|
console.error('复制失败:', err);
|
||||||
|
this.showError('复制失败,请手动选择内容');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
flashCopyButton() {
|
||||||
|
const copyBtn = document.getElementById('copy-btn');
|
||||||
|
copyBtn.classList.add('flash');
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
copyBtn.classList.remove('flash');
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
|
||||||
|
clearResults() {
|
||||||
|
const urlInput = document.getElementById('url-input');
|
||||||
|
const resultsElement = document.getElementById('results');
|
||||||
|
const errorElement = document.getElementById('error-message');
|
||||||
|
|
||||||
|
urlInput.value = '';
|
||||||
|
urlInput.classList.remove('error');
|
||||||
|
resultsElement.classList.remove('active');
|
||||||
|
errorElement.classList.remove('active');
|
||||||
|
|
||||||
|
document.getElementById('analyze-btn').classList.remove('ready');
|
||||||
|
document.getElementById('copy-btn').disabled = true;
|
||||||
|
document.getElementById('clear-btn').disabled = true;
|
||||||
|
|
||||||
|
this.currentUrl = '';
|
||||||
|
|
||||||
|
// 重置动画状态
|
||||||
|
const cards = document.querySelectorAll('.info-card');
|
||||||
|
cards.forEach(card => card.classList.remove('animate-in'));
|
||||||
|
|
||||||
|
this.showSuccessMessage('已清除所有内容');
|
||||||
|
}
|
||||||
|
|
||||||
|
createBackgroundEffects() {
|
||||||
|
const container = document.querySelector('.background-container');
|
||||||
|
|
||||||
|
// 创建各种背景效果层
|
||||||
|
const effects = [
|
||||||
|
'geometric-grid',
|
||||||
|
'neural-network',
|
||||||
|
'particle-system',
|
||||||
|
'scan-lines',
|
||||||
|
'holographic-overlay',
|
||||||
|
'data-stream',
|
||||||
|
'quantum-waves'
|
||||||
|
];
|
||||||
|
|
||||||
|
effects.forEach(effectClass => {
|
||||||
|
const layer = document.createElement('div');
|
||||||
|
layer.className = effectClass;
|
||||||
|
container.appendChild(layer);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
createInputGlow() {
|
||||||
|
const inputContainer = document.querySelector('.input-container');
|
||||||
|
|
||||||
|
// 创建光晕效果
|
||||||
|
const glow = document.createElement('div');
|
||||||
|
glow.className = 'input-glow';
|
||||||
|
inputContainer.appendChild(glow);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
if (glow.parentNode) {
|
||||||
|
glow.remove();
|
||||||
|
}
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
startScannerAnimation() {
|
||||||
|
const scanner = document.querySelector('.scanner');
|
||||||
|
if (scanner) {
|
||||||
|
scanner.classList.add('active');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stopScannerAnimation() {
|
||||||
|
const scanner = document.querySelector('.scanner');
|
||||||
|
if (scanner) {
|
||||||
|
scanner.classList.remove('active');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
initializeAnimations() {
|
||||||
|
// 初始化页面动画
|
||||||
|
const header = document.querySelector('.header');
|
||||||
|
const querySection = document.querySelector('.query-section');
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
header.classList.add('animate-in');
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
querySection.classList.add('animate-in');
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
|
||||||
|
showWelcomeMessage() {
|
||||||
|
const tips = [
|
||||||
|
'支持分析网页的标题、描述、图片等元信息',
|
||||||
|
'可以预览社交媒体分享时的显示效果',
|
||||||
|
'检测网页的SEO优化情况',
|
||||||
|
'分析Open Graph协议标签'
|
||||||
|
];
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
this.showSuccessMessage('欢迎使用链接OG信息分析器!');
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
// 显示提示信息
|
||||||
|
this.showTips(tips);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示提示信息
|
||||||
|
showTips(tips) {
|
||||||
|
const tipElement = document.getElementById('tip-message');
|
||||||
|
const tipText = tipElement.querySelector('.tip-text');
|
||||||
|
|
||||||
|
let currentTip = 0;
|
||||||
|
|
||||||
|
const showNextTip = () => {
|
||||||
|
tipText.textContent = tips[currentTip];
|
||||||
|
tipElement.classList.add('active');
|
||||||
|
tipElement.style.animation = 'fadeInUp 0.5s ease-out';
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
tipElement.style.animation = 'fadeOutDown 0.5s ease-in';
|
||||||
|
setTimeout(() => {
|
||||||
|
tipElement.classList.remove('active');
|
||||||
|
currentTip = (currentTip + 1) % tips.length;
|
||||||
|
}, 500);
|
||||||
|
}, 3000);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 首次显示
|
||||||
|
setTimeout(showNextTip, 2000);
|
||||||
|
|
||||||
|
// 每8秒显示一次
|
||||||
|
setInterval(showNextTip, 8000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 工具函数
|
||||||
|
function debounce(func, wait) {
|
||||||
|
let timeout;
|
||||||
|
return function executedFunction(...args) {
|
||||||
|
const later = () => {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
func(...args);
|
||||||
|
};
|
||||||
|
clearTimeout(timeout);
|
||||||
|
timeout = setTimeout(later, wait);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function throttle(func, limit) {
|
||||||
|
let inThrottle;
|
||||||
|
return function() {
|
||||||
|
const args = arguments;
|
||||||
|
const context = this;
|
||||||
|
if (!inThrottle) {
|
||||||
|
func.apply(context, args);
|
||||||
|
inThrottle = true;
|
||||||
|
setTimeout(() => inThrottle = false, limit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 页面加载完成后初始化
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
// 检查必要的DOM元素
|
||||||
|
const requiredElements = [
|
||||||
|
'url-input', 'analyze-btn', 'copy-btn', 'clear-btn',
|
||||||
|
'loading', 'results', 'error-message', 'tip-message'
|
||||||
|
];
|
||||||
|
|
||||||
|
const missingElements = requiredElements.filter(id => !document.getElementById(id));
|
||||||
|
|
||||||
|
if (missingElements.length > 0) {
|
||||||
|
console.error('缺少必要的DOM元素:', missingElements);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化应用
|
||||||
|
window.ogAnalyzer = new OGAnalyzer();
|
||||||
|
|
||||||
|
// 添加全局错误处理
|
||||||
|
window.addEventListener('error', (e) => {
|
||||||
|
console.error('全局错误:', e.error);
|
||||||
|
if (window.ogAnalyzer) {
|
||||||
|
window.ogAnalyzer.showError('发生未知错误,请刷新页面重试');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 添加网络状态监听
|
||||||
|
window.addEventListener('online', () => {
|
||||||
|
if (window.ogAnalyzer) {
|
||||||
|
window.ogAnalyzer.showSuccessMessage('网络连接已恢复');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('offline', () => {
|
||||||
|
if (window.ogAnalyzer) {
|
||||||
|
window.ogAnalyzer.showError('网络连接已断开');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 导出给其他模块使用
|
||||||
|
if (typeof module !== 'undefined' && module.exports) {
|
||||||
|
module.exports = { OGAnalyzer, debounce, throttle };
|
||||||
|
}
|
||||||
3
frontend/60sapi/实用功能/链接OG信息/接口集合.json
Normal file
3
frontend/60sapi/实用功能/链接OG信息/接口集合.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
[
|
||||||
|
"https://60s.api.shumengya.top"
|
||||||
|
]
|
||||||
66
frontend/60sapi/实用功能/链接OG信息/返回接口.json
Normal file
66
frontend/60sapi/实用功能/链接OG信息/返回接口.json
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "success",
|
||||||
|
"data": {
|
||||||
|
"url": "https://example.com",
|
||||||
|
"title": "示例网站标题",
|
||||||
|
"description": "这是一个示例网站的描述信息,用于展示OG标签解析功能。",
|
||||||
|
"image": "https://example.com/og-image.jpg",
|
||||||
|
"site_name": "示例网站",
|
||||||
|
"type": "website",
|
||||||
|
"locale": "zh_CN",
|
||||||
|
"author": "网站作者",
|
||||||
|
"keywords": "示例,网站,OG标签,元数据",
|
||||||
|
"favicon": "https://example.com/favicon.ico",
|
||||||
|
"canonical_url": "https://example.com",
|
||||||
|
"robots": "index,follow",
|
||||||
|
"viewport": "width=device-width, initial-scale=1.0",
|
||||||
|
"charset": "UTF-8",
|
||||||
|
"language": "zh-CN",
|
||||||
|
"published_time": "2024-01-01T00:00:00Z",
|
||||||
|
"modified_time": "2024-01-15T12:30:00Z",
|
||||||
|
"section": "技术",
|
||||||
|
"tags": ["前端", "元数据", "SEO"],
|
||||||
|
"twitter": {
|
||||||
|
"card": "summary_large_image",
|
||||||
|
"site": "@example",
|
||||||
|
"creator": "@author",
|
||||||
|
"title": "Twitter标题",
|
||||||
|
"description": "Twitter描述",
|
||||||
|
"image": "https://example.com/twitter-image.jpg"
|
||||||
|
},
|
||||||
|
"facebook": {
|
||||||
|
"app_id": "123456789",
|
||||||
|
"admins": "987654321"
|
||||||
|
},
|
||||||
|
"structured_data": {
|
||||||
|
"@context": "https://schema.org",
|
||||||
|
"@type": "WebPage",
|
||||||
|
"name": "示例网页",
|
||||||
|
"description": "示例网页描述",
|
||||||
|
"url": "https://example.com"
|
||||||
|
},
|
||||||
|
"meta_tags": {
|
||||||
|
"generator": "WordPress 6.0",
|
||||||
|
"theme-color": "#000000",
|
||||||
|
"msapplication-TileColor": "#ffffff",
|
||||||
|
"apple-mobile-web-app-capable": "yes",
|
||||||
|
"apple-mobile-web-app-status-bar-style": "default"
|
||||||
|
},
|
||||||
|
"performance": {
|
||||||
|
"load_time": 1.25,
|
||||||
|
"page_size": "2.3MB",
|
||||||
|
"requests_count": 45
|
||||||
|
},
|
||||||
|
"seo_score": {
|
||||||
|
"overall": 85,
|
||||||
|
"title_score": 90,
|
||||||
|
"description_score": 80,
|
||||||
|
"image_score": 85,
|
||||||
|
"structure_score": 88
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"timestamp": "2024-01-15T12:30:45Z",
|
||||||
|
"request_id": "req_123456789",
|
||||||
|
"processing_time": 0.85
|
||||||
|
}
|
||||||
335
frontend/aimodelapp/AI写诗小助手/index.html
Normal file
335
frontend/aimodelapp/AI写诗小助手/index.html
Normal file
@@ -0,0 +1,335 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>AI古诗生成器</title>
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: 'Microsoft YaHei', '微软雅黑', Arial, sans-serif;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
min-height: 100vh;
|
||||||
|
padding: 20px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 0 auto;
|
||||||
|
background: rgba(255, 255, 255, 0.95);
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 30px;
|
||||||
|
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
color: #4a5568;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
font-weight: 300;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
color: #718096;
|
||||||
|
font-size: 1rem;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #4a5568;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 15px;
|
||||||
|
border: 2px solid #e2e8f0;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 1rem;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
background: #f7fafc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #667eea;
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.textarea {
|
||||||
|
resize: vertical;
|
||||||
|
min-height: 120px;
|
||||||
|
font-family: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
width: 100%;
|
||||||
|
padding: 15px;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
margin-bottom: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 10px 25px rgba(102, 126, 234, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
cursor: not-allowed;
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-section {
|
||||||
|
margin-top: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-title {
|
||||||
|
font-size: 1.3rem;
|
||||||
|
color: #4a5568;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.poem-output {
|
||||||
|
background: #f7fafc;
|
||||||
|
border: 2px solid #e2e8f0;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 25px;
|
||||||
|
min-height: 200px;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
font-family: 'KaiTi', '楷体', serif;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
line-height: 2;
|
||||||
|
text-align: center;
|
||||||
|
color: #2d3748;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading {
|
||||||
|
display: none;
|
||||||
|
text-align: center;
|
||||||
|
color: #667eea;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
color: #e53e3e;
|
||||||
|
background: #fed7d7;
|
||||||
|
border: 1px solid #feb2b2;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
body {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
padding: 20px;
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input {
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.poem-output {
|
||||||
|
padding: 20px;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.title {
|
||||||
|
font-size: 1.8rem;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.poem-output {
|
||||||
|
font-size: 1rem;
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="header">
|
||||||
|
<h1 class="title">AI古诗生成器</h1>
|
||||||
|
<p class="subtitle">让人工智能为您创作优美的中国古诗</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- GitHub令牌已内置,无需用户输入 -->
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label" for="theme">作诗主题:</label>
|
||||||
|
<textarea
|
||||||
|
id="theme"
|
||||||
|
class="form-input textarea"
|
||||||
|
placeholder="请输入您想要的古诗主题,例如:春天的江南水乡、秋日的思乡之情、雪夜的孤独等..."
|
||||||
|
>春天的江南水乡</textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button id="generateBtn" class="btn">生成古诗</button>
|
||||||
|
|
||||||
|
<div class="result-section">
|
||||||
|
<h3 class="result-title">生成的古诗</h3>
|
||||||
|
<div id="loading" class="loading">正在创作中,请稍候...</div>
|
||||||
|
<div id="poemOutput" class="poem-output">点击"生成古诗"按钮,AI将为您创作优美的古诗</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// GitHub API 配置
|
||||||
|
const GITHUB_TOKEN = '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';
|
||||||
|
const endpoint = 'https://models.github.ai/inference/chat/completions';
|
||||||
|
const generateBtn = document.getElementById('generateBtn');
|
||||||
|
const loading = document.getElementById('loading');
|
||||||
|
const poemOutput = document.getElementById('poemOutput');
|
||||||
|
|
||||||
|
// 专业的古诗生成提示词
|
||||||
|
const createPoemPrompt = (theme) => {
|
||||||
|
return `你是一位精通中国古典诗词的大师,请根据用户提供的主题创作一首优美的古诗。
|
||||||
|
|
||||||
|
要求:
|
||||||
|
1. 严格遵循中国古诗的格律和韵律
|
||||||
|
2. 可以是五言绝句、七言绝句、五言律诗或七言律诗
|
||||||
|
3. 注重意境的营造,体现中国传统文化的美感
|
||||||
|
4. 用词典雅,富有诗意
|
||||||
|
5. 根据主题选择合适的风格(豪放、婉约、田园、边塞等)
|
||||||
|
6. 确保押韵和平仄协调
|
||||||
|
7. 请先给诗歌起一个优美的标题,然后换行写出诗歌内容
|
||||||
|
8. 格式:标题\n诗歌正文
|
||||||
|
9. 注意排版对齐,标题居中,诗歌正文左对齐
|
||||||
|
10. 诗歌内容必须是中文
|
||||||
|
|
||||||
|
主题:${theme}
|
||||||
|
|
||||||
|
请创作一首古诗:`;
|
||||||
|
};
|
||||||
|
|
||||||
|
generateBtn.addEventListener('click', async () => {
|
||||||
|
const theme = document.getElementById('theme').value.trim();
|
||||||
|
|
||||||
|
if (!theme) {
|
||||||
|
alert('请输入作诗主题');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示加载状态
|
||||||
|
generateBtn.disabled = true;
|
||||||
|
generateBtn.textContent = '创作中...';
|
||||||
|
loading.style.display = 'block';
|
||||||
|
poemOutput.textContent = '';
|
||||||
|
|
||||||
|
// 清除之前的错误信息
|
||||||
|
const existingError = document.querySelector('.error');
|
||||||
|
if (existingError) {
|
||||||
|
existingError.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
const requestBody = {
|
||||||
|
model: "openai/gpt-4o-mini",
|
||||||
|
messages: [{
|
||||||
|
role: "user",
|
||||||
|
content: createPoemPrompt(theme)
|
||||||
|
}],
|
||||||
|
temperature: 0.8,
|
||||||
|
max_tokens: 500
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(endpoint, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Accept': 'application/vnd.github+json',
|
||||||
|
'Authorization': `Bearer ${GITHUB_TOKEN}`,
|
||||||
|
'X-GitHub-Api-Version': '2022-11-28',
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(requestBody)
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data?.choices?.[0]?.message?.content) {
|
||||||
|
poemOutput.textContent = data.choices[0].message.content.trim();
|
||||||
|
} else {
|
||||||
|
throw new Error('AI响应格式异常');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('生成古诗时出错:', error);
|
||||||
|
|
||||||
|
// 显示错误信息
|
||||||
|
const errorDiv = document.createElement('div');
|
||||||
|
errorDiv.className = 'error';
|
||||||
|
errorDiv.textContent = `生成失败:${error.message}。请检查网络连接和访问令牌是否正确。`;
|
||||||
|
poemOutput.parentNode.appendChild(errorDiv);
|
||||||
|
|
||||||
|
poemOutput.textContent = '生成失败,请重试';
|
||||||
|
} finally {
|
||||||
|
// 恢复按钮状态
|
||||||
|
generateBtn.disabled = false;
|
||||||
|
generateBtn.textContent = '生成古诗';
|
||||||
|
loading.style.display = 'none';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 回车键快捷生成
|
||||||
|
document.getElementById('theme').addEventListener('keypress', (e) => {
|
||||||
|
if (e.key === 'Enter' && !e.shiftKey) {
|
||||||
|
e.preventDefault();
|
||||||
|
generateBtn.click();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
42
frontend/aimodelapp/AI变量命名助手/index.html
Normal file
42
frontend/aimodelapp/AI变量命名助手/index.html
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>AI变量命名助手</title>
|
||||||
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="header">
|
||||||
|
<h1 class="title">AI变量命名助手</h1>
|
||||||
|
<p class="subtitle">让AI帮您生成规范的变量名</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-section">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label" for="description">变量描述:</label>
|
||||||
|
<textarea
|
||||||
|
id="description"
|
||||||
|
class="form-input textarea"
|
||||||
|
placeholder="请描述变量的用途,例如:用户的姓名、商品的价格、数据库连接状态等..."
|
||||||
|
>用户的姓名</textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 命名规范选择已移除,将生成所有5种规范的建议 -->
|
||||||
|
|
||||||
|
<button id="generateBtn" class="btn">生成变量名</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="result-section">
|
||||||
|
<h3 class="result-title">推荐的变量名</h3>
|
||||||
|
<div id="loading" class="loading">正在生成中,请稍候...</div>
|
||||||
|
<div id="suggestions" class="suggestions-container">
|
||||||
|
<div class="placeholder">点击"生成变量名"按钮,AI将为您推荐合适的变量名</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="script.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
305
frontend/aimodelapp/AI变量命名助手/script.js
Normal file
305
frontend/aimodelapp/AI变量命名助手/script.js
Normal file
@@ -0,0 +1,305 @@
|
|||||||
|
// GitHub Models API 配置
|
||||||
|
const GITHUB_TOKEN = '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';
|
||||||
|
const API_URL = 'https://models.github.ai/inference/chat/completions';
|
||||||
|
const MODEL_NAME = 'openai/gpt-4o-mini';
|
||||||
|
|
||||||
|
// DOM 元素
|
||||||
|
const descriptionInput = document.getElementById('description');
|
||||||
|
const generateBtn = document.getElementById('generateBtn');
|
||||||
|
const loadingDiv = document.getElementById('loading');
|
||||||
|
const suggestionsContainer = document.getElementById('suggestions');
|
||||||
|
|
||||||
|
// 命名规范转换函数
|
||||||
|
const namingConventions = {
|
||||||
|
camelCase: (words) => {
|
||||||
|
if (words.length === 0) return '';
|
||||||
|
return words[0].toLowerCase() + words.slice(1).map(word =>
|
||||||
|
word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()
|
||||||
|
).join('');
|
||||||
|
},
|
||||||
|
|
||||||
|
PascalCase: (words) => {
|
||||||
|
return words.map(word =>
|
||||||
|
word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()
|
||||||
|
).join('');
|
||||||
|
},
|
||||||
|
|
||||||
|
snake_case: (words) => {
|
||||||
|
return words.map(word => word.toLowerCase()).join('_');
|
||||||
|
},
|
||||||
|
|
||||||
|
'kebab-case': (words) => {
|
||||||
|
return words.map(word => word.toLowerCase()).join('-');
|
||||||
|
},
|
||||||
|
|
||||||
|
CONSTANT_CASE: (words) => {
|
||||||
|
return words.map(word => word.toUpperCase()).join('_');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 创建AI提示词
|
||||||
|
function createNamingPrompt(description) {
|
||||||
|
return `你是一个专业的变量命名助手。请根据以下描述为变量生成合适的名称:
|
||||||
|
|
||||||
|
描述:${description}
|
||||||
|
|
||||||
|
请为每种命名规范生成3个变量名建议:
|
||||||
|
1. camelCase (驼峰命名法)
|
||||||
|
2. PascalCase (帕斯卡命名法)
|
||||||
|
3. snake_case (下划线命名法)
|
||||||
|
4. kebab-case (短横线命名法)
|
||||||
|
5. CONSTANT_CASE (常量命名法)
|
||||||
|
|
||||||
|
要求:
|
||||||
|
- 变量名要准确反映功能和用途
|
||||||
|
- 严格遵循各自的命名规范
|
||||||
|
- 避免使用缩写,除非是广泛认知的缩写
|
||||||
|
- 名称要简洁但具有描述性
|
||||||
|
- 考虑代码的可读性和维护性
|
||||||
|
|
||||||
|
请按以下JSON格式返回:
|
||||||
|
{
|
||||||
|
"suggestions": {
|
||||||
|
"camelCase": [
|
||||||
|
{"name": "变量名1", "description": "解释说明1"},
|
||||||
|
{"name": "变量名2", "description": "解释说明2"},
|
||||||
|
{"name": "变量名3", "description": "解释说明3"}
|
||||||
|
],
|
||||||
|
"PascalCase": [
|
||||||
|
{"name": "变量名1", "description": "解释说明1"},
|
||||||
|
{"name": "变量名2", "description": "解释说明2"},
|
||||||
|
{"name": "变量名3", "description": "解释说明3"}
|
||||||
|
],
|
||||||
|
"snake_case": [
|
||||||
|
{"name": "变量名1", "description": "解释说明1"},
|
||||||
|
{"name": "变量名2", "description": "解释说明2"},
|
||||||
|
{"name": "变量名3", "description": "解释说明3"}
|
||||||
|
],
|
||||||
|
"kebab-case": [
|
||||||
|
{"name": "变量名1", "description": "解释说明1"},
|
||||||
|
{"name": "变量名2", "description": "解释说明2"},
|
||||||
|
{"name": "变量名3", "description": "解释说明3"}
|
||||||
|
],
|
||||||
|
"CONSTANT_CASE": [
|
||||||
|
{"name": "变量名1", "description": "解释说明1"},
|
||||||
|
{"name": "变量名2", "description": "解释说明2"},
|
||||||
|
{"name": "变量名3", "description": "解释说明3"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
只返回JSON格式的结果,不要包含其他文字。`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用GitHub Models API
|
||||||
|
async function callGitHubModelsAPI(prompt) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(API_URL, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': `Bearer ${GITHUB_TOKEN}`
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
messages: [
|
||||||
|
{
|
||||||
|
role: 'user',
|
||||||
|
content: prompt
|
||||||
|
}
|
||||||
|
],
|
||||||
|
model: MODEL_NAME,
|
||||||
|
temperature: 0.7,
|
||||||
|
max_tokens: 1000
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`API请求失败: ${response.status} ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
return data.choices[0].message.content;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('API调用错误:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析AI响应
|
||||||
|
function parseAIResponse(response) {
|
||||||
|
try {
|
||||||
|
// 尝试直接解析JSON
|
||||||
|
const parsed = JSON.parse(response);
|
||||||
|
return parsed.suggestions || {};
|
||||||
|
} catch (error) {
|
||||||
|
// 如果直接解析失败,尝试提取JSON部分
|
||||||
|
const jsonMatch = response.match(/\{[\s\S]*\}/);
|
||||||
|
if (jsonMatch) {
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(jsonMatch[0]);
|
||||||
|
return parsed.suggestions || {};
|
||||||
|
} catch (e) {
|
||||||
|
console.error('JSON解析失败:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果JSON解析失败,返回空对象
|
||||||
|
console.error('无法解析AI响应:', response);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 显示建议
|
||||||
|
function displaySuggestions(suggestions) {
|
||||||
|
suggestionsContainer.innerHTML = '';
|
||||||
|
|
||||||
|
if (!suggestions || Object.keys(suggestions).length === 0) {
|
||||||
|
suggestionsContainer.innerHTML = '<div class="placeholder">暂无建议,请尝试重新生成</div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 命名规范的显示名称
|
||||||
|
const conventionNames = {
|
||||||
|
'camelCase': 'camelCase (驼峰命名法)',
|
||||||
|
'PascalCase': 'PascalCase (帕斯卡命名法)',
|
||||||
|
'snake_case': 'snake_case (下划线命名法)',
|
||||||
|
'kebab-case': 'kebab-case (短横线命名法)',
|
||||||
|
'CONSTANT_CASE': 'CONSTANT_CASE (常量命名法)'
|
||||||
|
};
|
||||||
|
|
||||||
|
// 按命名规范分组显示
|
||||||
|
Object.keys(suggestions).forEach(convention => {
|
||||||
|
if (suggestions[convention] && suggestions[convention].length > 0) {
|
||||||
|
// 创建分组标题
|
||||||
|
const groupTitle = document.createElement('div');
|
||||||
|
groupTitle.className = 'convention-group-title';
|
||||||
|
groupTitle.textContent = conventionNames[convention] || convention;
|
||||||
|
suggestionsContainer.appendChild(groupTitle);
|
||||||
|
|
||||||
|
// 显示该规范下的建议
|
||||||
|
suggestions[convention].forEach(suggestion => {
|
||||||
|
const suggestionElement = document.createElement('div');
|
||||||
|
suggestionElement.className = 'suggestion-item';
|
||||||
|
suggestionElement.innerHTML = `
|
||||||
|
<div class="variable-name">${suggestion.name}</div>
|
||||||
|
<div class="variable-description">${suggestion.description}</div>
|
||||||
|
<button class="copy-btn" onclick="copyToClipboard('${suggestion.name}', this)">复制</button>
|
||||||
|
`;
|
||||||
|
suggestionsContainer.appendChild(suggestionElement);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 复制到剪贴板
|
||||||
|
function copyToClipboard(text, button) {
|
||||||
|
navigator.clipboard.writeText(text).then(() => {
|
||||||
|
showSuccessToast('已复制到剪贴板');
|
||||||
|
button.textContent = '已复制';
|
||||||
|
setTimeout(() => {
|
||||||
|
button.textContent = '复制';
|
||||||
|
}, 2000);
|
||||||
|
}).catch(err => {
|
||||||
|
console.error('复制失败:', err);
|
||||||
|
// 备用复制方法
|
||||||
|
const textArea = document.createElement('textarea');
|
||||||
|
textArea.value = text;
|
||||||
|
document.body.appendChild(textArea);
|
||||||
|
textArea.select();
|
||||||
|
try {
|
||||||
|
document.execCommand('copy');
|
||||||
|
showSuccessToast('已复制到剪贴板');
|
||||||
|
button.textContent = '已复制';
|
||||||
|
setTimeout(() => {
|
||||||
|
button.textContent = '复制';
|
||||||
|
}, 2000);
|
||||||
|
} catch (e) {
|
||||||
|
showErrorMessage('复制失败,请手动复制');
|
||||||
|
}
|
||||||
|
document.body.removeChild(textArea);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示成功提示
|
||||||
|
function showSuccessToast(message) {
|
||||||
|
const toast = document.createElement('div');
|
||||||
|
toast.className = 'success-toast';
|
||||||
|
toast.textContent = message;
|
||||||
|
document.body.appendChild(toast);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
toast.classList.add('show');
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
toast.classList.remove('show');
|
||||||
|
setTimeout(() => {
|
||||||
|
document.body.removeChild(toast);
|
||||||
|
}, 300);
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示错误信息
|
||||||
|
function showErrorMessage(message) {
|
||||||
|
const errorDiv = document.createElement('div');
|
||||||
|
errorDiv.className = 'error';
|
||||||
|
errorDiv.textContent = message;
|
||||||
|
suggestionsContainer.innerHTML = '';
|
||||||
|
suggestionsContainer.appendChild(errorDiv);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示加载状态
|
||||||
|
function showLoading(show) {
|
||||||
|
loadingDiv.style.display = show ? 'block' : 'none';
|
||||||
|
generateBtn.disabled = show;
|
||||||
|
generateBtn.textContent = show ? '生成中...' : '生成变量名';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成变量名建议
|
||||||
|
async function generateSuggestions() {
|
||||||
|
const description = descriptionInput.value.trim();
|
||||||
|
|
||||||
|
if (!description) {
|
||||||
|
showErrorMessage('请输入变量描述');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
showLoading(true);
|
||||||
|
suggestionsContainer.innerHTML = '';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const prompt = createNamingPrompt(description);
|
||||||
|
const response = await callGitHubModelsAPI(prompt);
|
||||||
|
const suggestions = parseAIResponse(response);
|
||||||
|
|
||||||
|
displaySuggestions(suggestions);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('生成建议失败:', error);
|
||||||
|
showErrorMessage(`生成失败: ${error.message}`);
|
||||||
|
} finally {
|
||||||
|
showLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 事件监听器
|
||||||
|
generateBtn.addEventListener('click', generateSuggestions);
|
||||||
|
|
||||||
|
// 回车键生成
|
||||||
|
descriptionInput.addEventListener('keypress', (e) => {
|
||||||
|
if (e.key === 'Enter' && !e.shiftKey) {
|
||||||
|
e.preventDefault();
|
||||||
|
generateSuggestions();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 页面加载完成后的初始化
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
// 设置默认占位符
|
||||||
|
suggestionsContainer.innerHTML = '<div class="placeholder">请输入变量描述,然后点击生成按钮获取所有命名规范的建议</div>';
|
||||||
|
});
|
||||||
|
|
||||||
|
// 导出函数供HTML调用
|
||||||
|
window.copyToClipboard = copyToClipboard;
|
||||||
|
window.generateSuggestions = generateSuggestions;
|
||||||
365
frontend/aimodelapp/AI变量命名助手/styles.css
Normal file
365
frontend/aimodelapp/AI变量命名助手/styles.css
Normal file
@@ -0,0 +1,365 @@
|
|||||||
|
/* 全局样式重置 */
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 主体样式 */
|
||||||
|
body {
|
||||||
|
font-family: 'Microsoft YaHei', '微软雅黑', 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
min-height: 100vh;
|
||||||
|
padding: 20px;
|
||||||
|
color: #333;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 容器样式 */
|
||||||
|
.container {
|
||||||
|
max-width: 700px;
|
||||||
|
margin: 0 auto;
|
||||||
|
background: rgba(255, 255, 255, 0.95);
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 30px;
|
||||||
|
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 头部样式 */
|
||||||
|
.header {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
color: #4a5568;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
font-weight: 300;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
color: #718096;
|
||||||
|
font-size: 1rem;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 表单样式 */
|
||||||
|
.form-section {
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #4a5568;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 15px;
|
||||||
|
border: 2px solid #e2e8f0;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 1rem;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
background: #f7fafc;
|
||||||
|
font-family: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #667eea;
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.textarea {
|
||||||
|
resize: vertical;
|
||||||
|
min-height: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select {
|
||||||
|
cursor: pointer;
|
||||||
|
appearance: none;
|
||||||
|
background-image: url('data:image/svg+xml;charset=US-ASCII,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 4 5"><path fill="%23666" d="M2 0L0 2h4zm0 5L0 3h4z"/></svg>');
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: right 15px center;
|
||||||
|
background-size: 12px;
|
||||||
|
padding-right: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 按钮样式 */
|
||||||
|
.btn {
|
||||||
|
width: 100%;
|
||||||
|
padding: 15px;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
margin-bottom: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 10px 25px rgba(102, 126, 234, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
cursor: not-allowed;
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 结果区域样式 */
|
||||||
|
.result-section {
|
||||||
|
margin-top: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-title {
|
||||||
|
font-size: 1.3rem;
|
||||||
|
color: #4a5568;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading {
|
||||||
|
display: none;
|
||||||
|
text-align: center;
|
||||||
|
color: #667eea;
|
||||||
|
font-style: italic;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.suggestions-container {
|
||||||
|
background: #f7fafc;
|
||||||
|
border: 2px solid #e2e8f0;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 20px;
|
||||||
|
min-height: 150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.placeholder {
|
||||||
|
text-align: center;
|
||||||
|
color: #a0aec0;
|
||||||
|
font-style: italic;
|
||||||
|
padding: 40px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 分组标题样式 */
|
||||||
|
.convention-group-title {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #4a5568;
|
||||||
|
margin: 20px 0 10px 0;
|
||||||
|
padding: 8px 12px;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
color: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.convention-group-title:first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 建议项样式 */
|
||||||
|
.suggestion-item {
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 15px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.suggestion-item:hover {
|
||||||
|
border-color: #667eea;
|
||||||
|
box-shadow: 0 2px 8px rgba(102, 126, 234, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.suggestion-item:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.variable-name {
|
||||||
|
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #2d3748;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.variable-description {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: #718096;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.copy-btn {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
right: 10px;
|
||||||
|
background: #667eea;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 5px 10px;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
cursor: pointer;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.suggestion-item:hover .copy-btn {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.copy-btn:hover {
|
||||||
|
background: #5a67d8;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 错误样式 */
|
||||||
|
.error {
|
||||||
|
color: #e53e3e;
|
||||||
|
background: #fed7d7;
|
||||||
|
border: 1px solid #feb2b2;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 成功提示 */
|
||||||
|
.success-toast {
|
||||||
|
position: fixed;
|
||||||
|
top: 20px;
|
||||||
|
right: 20px;
|
||||||
|
background: #48bb78;
|
||||||
|
color: white;
|
||||||
|
padding: 12px 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||||
|
z-index: 1000;
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(100%);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.success-toast.show {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 响应式设计 */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
body {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
padding: 20px;
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input {
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.suggestions-container {
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.suggestion-item {
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.copy-btn {
|
||||||
|
position: static;
|
||||||
|
opacity: 1;
|
||||||
|
margin-top: 8px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.title {
|
||||||
|
font-size: 1.8rem;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.suggestion-item {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.variable-name {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.variable-description {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 动画效果 */
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(10px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.suggestion-item {
|
||||||
|
animation: fadeIn 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 加载动画 */
|
||||||
|
@keyframes pulse {
|
||||||
|
0%, 100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading {
|
||||||
|
animation: pulse 1.5s ease-in-out infinite;
|
||||||
|
}
|
||||||
56
frontend/aimodelapp/AI姓名评测/index.html
Normal file
56
frontend/aimodelapp/AI姓名评测/index.html
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>AI姓名评测</title>
|
||||||
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="header">
|
||||||
|
<h1 class="title">AI姓名评测</h1>
|
||||||
|
<p class="subtitle">智能分析姓名的稀有度、音韵美感与寓意内涵</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label" for="nameInput">请输入姓名:</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="nameInput"
|
||||||
|
class="form-input"
|
||||||
|
placeholder="请输入要分析的姓名,例如:张三、李雅婷、王子轩等..."
|
||||||
|
maxlength="10"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button id="analyzeBtn" class="btn">开始分析</button>
|
||||||
|
|
||||||
|
<div class="result-section">
|
||||||
|
<h3 class="result-title">分析结果</h3>
|
||||||
|
<div id="loading" class="loading">正在分析中,请稍候...</div>
|
||||||
|
|
||||||
|
<div id="resultContainer" class="result-container">
|
||||||
|
<div class="result-card">
|
||||||
|
<h4 class="card-title">稀有度评分</h4>
|
||||||
|
<div id="rarityScore" class="score-display">--%</div>
|
||||||
|
<div id="rarityDesc" class="score-desc">点击"开始分析"查看结果</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="result-card">
|
||||||
|
<h4 class="card-title">音韵评价</h4>
|
||||||
|
<div id="phoneticScore" class="score-display">--%</div>
|
||||||
|
<div id="phoneticDesc" class="score-desc">点击"开始分析"查看结果</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="result-card">
|
||||||
|
<h4 class="card-title">含义解读</h4>
|
||||||
|
<div id="meaningAnalysis" class="meaning-content">点击"开始分析"查看姓名的深层寓意</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="script.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
291
frontend/aimodelapp/AI姓名评测/script.js
Normal file
291
frontend/aimodelapp/AI姓名评测/script.js
Normal file
@@ -0,0 +1,291 @@
|
|||||||
|
// GitHub API 配置
|
||||||
|
const GITHUB_TOKEN = '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';
|
||||||
|
const endpoint = 'https://models.github.ai/inference/chat/completions';
|
||||||
|
const model = 'deepseek/DeepSeek-V3-0324';
|
||||||
|
|
||||||
|
// DOM 元素
|
||||||
|
const nameInput = document.getElementById('nameInput');
|
||||||
|
const analyzeBtn = document.getElementById('analyzeBtn');
|
||||||
|
const loading = document.getElementById('loading');
|
||||||
|
const rarityScore = document.getElementById('rarityScore');
|
||||||
|
const rarityDesc = document.getElementById('rarityDesc');
|
||||||
|
const phoneticScore = document.getElementById('phoneticScore');
|
||||||
|
const phoneticDesc = document.getElementById('phoneticDesc');
|
||||||
|
const meaningAnalysis = document.getElementById('meaningAnalysis');
|
||||||
|
|
||||||
|
// 专业的姓名分析提示词
|
||||||
|
const createNameAnalysisPrompt = (name) => {
|
||||||
|
return `你是一位专业的姓名学专家和语言学家,请对输入的姓名进行全面分析。请直接输出分析结果,不要包含任何思考过程或<think>标签。
|
||||||
|
|
||||||
|
姓名:${name}
|
||||||
|
|
||||||
|
请按照以下格式严格输出分析结果:
|
||||||
|
|
||||||
|
【稀有度评分】
|
||||||
|
评分:X%
|
||||||
|
评价:[对稀有度的详细说明,包括姓氏和名字的常见程度分析]
|
||||||
|
|
||||||
|
【音韵评价】
|
||||||
|
评分:X%
|
||||||
|
评价:[对音韵美感的分析,包括声调搭配、读音流畅度、音律和谐度等]
|
||||||
|
|
||||||
|
【含义解读】
|
||||||
|
[详细分析姓名的寓意内涵,包括:
|
||||||
|
1. 姓氏的历史渊源和文化背景
|
||||||
|
2. 名字各字的含义和象征
|
||||||
|
3. 整体姓名的寓意组合
|
||||||
|
4. 可能体现的父母期望或文化内涵
|
||||||
|
5. 与传统文化、诗词典故的关联等]
|
||||||
|
|
||||||
|
要求:
|
||||||
|
1. 评分必须是1-100的整数百分比,要有明显区分度,避免雷同
|
||||||
|
2. 分析要专业、客观、有依据,评分要根据实际情况有所差异
|
||||||
|
3. 含义解读要详细深入,至少150字
|
||||||
|
4. 严格按照上述格式输出,不要添加思考过程、<think>标签或其他内容
|
||||||
|
5. 如果是生僻字或罕见姓名,要特别说明
|
||||||
|
6. 直接输出最终结果,不要显示推理过程`;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 解析AI返回的分析结果
|
||||||
|
function parseAnalysisResult(content) {
|
||||||
|
const result = {
|
||||||
|
rarityScore: '--%',
|
||||||
|
rarityDesc: '解析失败',
|
||||||
|
phoneticScore: '--%',
|
||||||
|
phoneticDesc: '解析失败',
|
||||||
|
meaningAnalysis: '解析失败'
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 过滤掉DeepSeek的思考标签内容
|
||||||
|
let cleanContent = content.replace(/<think>[\s\S]*?<\/think>/gi, '');
|
||||||
|
cleanContent = cleanContent.replace(/<think>[\s\S]*$/gi, ''); // 处理未闭合的think标签
|
||||||
|
cleanContent = cleanContent.trim();
|
||||||
|
|
||||||
|
console.log('清理后的内容:', cleanContent);
|
||||||
|
|
||||||
|
// 提取稀有度评分(百分比格式)
|
||||||
|
const rarityMatch = cleanContent.match(/【稀有度评分】[\s\S]*?评分:(\d+)%[\s\S]*?评价:([\s\S]*?)(?=【|$)/);
|
||||||
|
if (rarityMatch) {
|
||||||
|
result.rarityScore = rarityMatch[1] + '%';
|
||||||
|
result.rarityDesc = rarityMatch[2].trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提取音韵评价(百分比格式)
|
||||||
|
const phoneticMatch = cleanContent.match(/【音韵评价】[\s\S]*?评分:(\d+)%[\s\S]*?评价:([\s\S]*?)(?=【|$)/);
|
||||||
|
if (phoneticMatch) {
|
||||||
|
result.phoneticScore = phoneticMatch[1] + '%';
|
||||||
|
result.phoneticDesc = phoneticMatch[2].trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提取含义解读
|
||||||
|
const meaningMatch = cleanContent.match(/【含义解读】[\s\S]*?\n([\s\S]+)$/);
|
||||||
|
if (meaningMatch) {
|
||||||
|
result.meaningAnalysis = meaningMatch[1].trim();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('解析结果时出错:', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 简单的markdown解析函数
|
||||||
|
function parseMarkdown(text) {
|
||||||
|
if (!text || typeof text !== 'string') return text;
|
||||||
|
|
||||||
|
// 处理加粗 **text** 或 __text__
|
||||||
|
let parsed = text.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
|
||||||
|
parsed = parsed.replace(/__(.*?)__/g, '<strong>$1</strong>');
|
||||||
|
|
||||||
|
// 处理斜体 *text* 或 _text_
|
||||||
|
parsed = parsed.replace(/\*(.*?)\*/g, '<em>$1</em>');
|
||||||
|
parsed = parsed.replace(/_(.*?)_/g, '<em>$1</em>');
|
||||||
|
|
||||||
|
// 处理无序列表
|
||||||
|
const lines = parsed.split('\n');
|
||||||
|
let inList = false;
|
||||||
|
let result = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < lines.length; i++) {
|
||||||
|
const line = lines[i].trim();
|
||||||
|
|
||||||
|
// 检查是否是列表项(以 - 开头,后面跟空格)
|
||||||
|
if (line.match(/^-\s+/)) {
|
||||||
|
if (!inList) {
|
||||||
|
result.push('<ul>');
|
||||||
|
inList = true;
|
||||||
|
}
|
||||||
|
// 移除开头的 "- " 并包装为 <li>
|
||||||
|
const listContent = line.replace(/^-\s+/, '');
|
||||||
|
result.push(`<li>${listContent}</li>`);
|
||||||
|
} else {
|
||||||
|
if (inList) {
|
||||||
|
result.push('</ul>');
|
||||||
|
inList = false;
|
||||||
|
}
|
||||||
|
if (line) {
|
||||||
|
result.push(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果最后还在列表中,需要关闭列表
|
||||||
|
if (inList) {
|
||||||
|
result.push('</ul>');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重新组合,用 <br> 连接非列表行
|
||||||
|
parsed = result.join('<br>');
|
||||||
|
|
||||||
|
// 清理多余的 <br> 标签(在列表前后)
|
||||||
|
parsed = parsed.replace(/<br><ul>/g, '<ul>');
|
||||||
|
parsed = parsed.replace(/<\/ul><br>/g, '</ul>');
|
||||||
|
parsed = parsed.replace(/<br><li>/g, '<li>');
|
||||||
|
parsed = parsed.replace(/<\/li><br>/g, '</li>');
|
||||||
|
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新显示结果
|
||||||
|
function updateResults(result) {
|
||||||
|
rarityScore.textContent = result.rarityScore;
|
||||||
|
rarityDesc.innerHTML = parseMarkdown(result.rarityDesc);
|
||||||
|
phoneticScore.textContent = result.phoneticScore;
|
||||||
|
phoneticDesc.innerHTML = parseMarkdown(result.phoneticDesc);
|
||||||
|
meaningAnalysis.innerHTML = parseMarkdown(result.meaningAnalysis);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置结果显示
|
||||||
|
function resetResults() {
|
||||||
|
rarityScore.textContent = '--%';
|
||||||
|
rarityDesc.innerHTML = '点击"开始分析"查看结果';
|
||||||
|
phoneticScore.textContent = '--%';
|
||||||
|
phoneticDesc.innerHTML = '点击"开始分析"查看结果';
|
||||||
|
meaningAnalysis.innerHTML = '点击"开始分析"查看姓名的深层寓意';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示错误信息
|
||||||
|
function showError(message) {
|
||||||
|
// 清除之前的错误信息
|
||||||
|
const existingError = document.querySelector('.error');
|
||||||
|
if (existingError) {
|
||||||
|
existingError.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建新的错误信息
|
||||||
|
const errorDiv = document.createElement('div');
|
||||||
|
errorDiv.className = 'error';
|
||||||
|
errorDiv.textContent = `分析失败:${message}。`;
|
||||||
|
document.querySelector('.result-section').appendChild(errorDiv);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 姓名验证
|
||||||
|
function validateName(name) {
|
||||||
|
if (!name) {
|
||||||
|
return '请输入姓名';
|
||||||
|
}
|
||||||
|
if (name.length < 2) {
|
||||||
|
return '姓名至少需要2个字符';
|
||||||
|
}
|
||||||
|
if (name.length > 10) {
|
||||||
|
return '姓名不能超过10个字符';
|
||||||
|
}
|
||||||
|
if (!/^[\u4e00-\u9fa5a-zA-Z]+$/.test(name)) {
|
||||||
|
return '姓名只能包含中文或英文字符';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 主要分析函数
|
||||||
|
async function analyzeName() {
|
||||||
|
const name = nameInput.value.trim();
|
||||||
|
|
||||||
|
// 验证输入
|
||||||
|
const validationError = validateName(name);
|
||||||
|
if (validationError) {
|
||||||
|
alert(validationError);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示加载状态
|
||||||
|
analyzeBtn.disabled = true;
|
||||||
|
analyzeBtn.textContent = '分析中...';
|
||||||
|
loading.style.display = 'block';
|
||||||
|
resetResults();
|
||||||
|
|
||||||
|
// 清除之前的错误信息
|
||||||
|
const existingError = document.querySelector('.error');
|
||||||
|
if (existingError) {
|
||||||
|
existingError.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
const requestBody = {
|
||||||
|
model: model,
|
||||||
|
messages: [{
|
||||||
|
role: "user",
|
||||||
|
content: createNameAnalysisPrompt(name)
|
||||||
|
}],
|
||||||
|
temperature: 0.7,
|
||||||
|
max_tokens: 1000
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(endpoint, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Accept': 'application/vnd.github+json',
|
||||||
|
'Authorization': `Bearer ${GITHUB_TOKEN}`,
|
||||||
|
'X-GitHub-Api-Version': '2022-11-28',
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(requestBody)
|
||||||
|
});
|
||||||
|
|
||||||
|
// 检查HTTP状态码
|
||||||
|
if (response.status === 429) {
|
||||||
|
throw new Error('短时间内请求次数过多,请休息一下!');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`请求失败: ${response.status} ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data?.choices?.[0]?.message?.content) {
|
||||||
|
const analysisResult = parseAnalysisResult(data.choices[0].message.content.trim());
|
||||||
|
updateResults(analysisResult);
|
||||||
|
} else {
|
||||||
|
throw new Error('AI响应格式异常');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('分析姓名时出错:', error);
|
||||||
|
showError(error.message);
|
||||||
|
resetResults();
|
||||||
|
} finally {
|
||||||
|
// 恢复按钮状态
|
||||||
|
analyzeBtn.disabled = false;
|
||||||
|
analyzeBtn.textContent = '开始分析';
|
||||||
|
loading.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 事件监听器
|
||||||
|
analyzeBtn.addEventListener('click', analyzeName);
|
||||||
|
|
||||||
|
// 回车键快捷分析
|
||||||
|
nameInput.addEventListener('keypress', (e) => {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
e.preventDefault();
|
||||||
|
analyzeName();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 输入框内容变化时清除错误信息
|
||||||
|
nameInput.addEventListener('input', () => {
|
||||||
|
const existingError = document.querySelector('.error');
|
||||||
|
if (existingError) {
|
||||||
|
existingError.remove();
|
||||||
|
}
|
||||||
|
});
|
||||||
249
frontend/aimodelapp/AI姓名评测/styles.css
Normal file
249
frontend/aimodelapp/AI姓名评测/styles.css
Normal file
@@ -0,0 +1,249 @@
|
|||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: 'Microsoft YaHei', '微软雅黑', Arial, sans-serif;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
min-height: 100vh;
|
||||||
|
padding: 20px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 700px;
|
||||||
|
margin: 0 auto;
|
||||||
|
background: rgba(255, 255, 255, 0.95);
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 30px;
|
||||||
|
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
color: #4a5568;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
font-weight: 300;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
color: #718096;
|
||||||
|
font-size: 1rem;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #4a5568;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 15px;
|
||||||
|
border: 2px solid #e2e8f0;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 1rem;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
background: #f7fafc;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #667eea;
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
width: 100%;
|
||||||
|
padding: 15px;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
margin-bottom: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 10px 25px rgba(102, 126, 234, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
cursor: not-allowed;
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-section {
|
||||||
|
margin-top: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-title {
|
||||||
|
font-size: 1.3rem;
|
||||||
|
color: #4a5568;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading {
|
||||||
|
display: none;
|
||||||
|
text-align: center;
|
||||||
|
color: #667eea;
|
||||||
|
font-style: italic;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-container {
|
||||||
|
display: grid;
|
||||||
|
gap: 20px;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-card {
|
||||||
|
background: #f7fafc;
|
||||||
|
border: 2px solid #e2e8f0;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 20px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-card:hover {
|
||||||
|
border-color: #667eea;
|
||||||
|
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-title {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
color: #4a5568;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-display {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
background-clip: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-desc {
|
||||||
|
text-align: center;
|
||||||
|
color: #718096;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meaning-content {
|
||||||
|
color: #2d3748;
|
||||||
|
line-height: 1.8;
|
||||||
|
font-size: 1rem;
|
||||||
|
text-align: left;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
color: #e53e3e;
|
||||||
|
background: #fed7d7;
|
||||||
|
border: 1px solid #feb2b2;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 平板和桌面端优化 */
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.result-container {
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-card:last-child {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 手机端优化 */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
body {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
padding: 20px;
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input {
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-card {
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-display {
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.title {
|
||||||
|
font-size: 1.8rem;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-card {
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-display {
|
||||||
|
font-size: 1.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meaning-content {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
12
frontend/aimodelapp/生成要求.txt
Normal file
12
frontend/aimodelapp/生成要求.txt
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
1.写一个静态网页来调用github model 来完成应用功能
|
||||||
|
2.网页清新风格,颜色随意,app风格,适配手机端和电脑端,优先优化手机端用户体验
|
||||||
|
3.默认github的调用密钥是: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
|
||||||
|
4.默认调用AI模型为 "openai/gpt-4o-mini"
|
||||||
|
5.把js,css,html分开储存,每个功能分每个模块,避免单个文件过大问题
|
||||||
|
6.默认github ai调用模型地址是:'https://models.github.ai/inference/chat/completions'
|
||||||
|
比如这样:
|
||||||
|
// GitHub API 配置
|
||||||
|
const GITHUB_TOKEN = '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';
|
||||||
|
const endpoint = 'https://models.github.ai/inference/chat/completions';
|
||||||
|
const model = 'deepseek/DeepSeek-V3-0324';
|
||||||
|
|
||||||
@@ -1,190 +0,0 @@
|
|||||||
/* 背景样式文件 */
|
|
||||||
|
|
||||||
/* 主体背景 */
|
|
||||||
body {
|
|
||||||
background: linear-gradient(135deg, #e8f5e8 0%, #c8e6c9 50%, #a5d6a7 100%);
|
|
||||||
background-attachment: fixed;
|
|
||||||
background-size: 400% 400%;
|
|
||||||
animation: gradientShift 15s ease infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 背景动画 */
|
|
||||||
@keyframes gradientShift {
|
|
||||||
0% {
|
|
||||||
background-position: 0% 50%;
|
|
||||||
}
|
|
||||||
50% {
|
|
||||||
background-position: 100% 50%;
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
background-position: 0% 50%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 装饰性背景元素 */
|
|
||||||
body::before {
|
|
||||||
content: '';
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background-image:
|
|
||||||
radial-gradient(circle at 20% 80%, rgba(76, 175, 80, 0.1) 0%, transparent 50%),
|
|
||||||
radial-gradient(circle at 80% 20%, rgba(129, 199, 132, 0.1) 0%, transparent 50%),
|
|
||||||
radial-gradient(circle at 40% 40%, rgba(165, 214, 167, 0.08) 0%, transparent 50%);
|
|
||||||
pointer-events: none;
|
|
||||||
z-index: -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 浮动装饰圆点 */
|
|
||||||
body::after {
|
|
||||||
content: '';
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background-image:
|
|
||||||
radial-gradient(2px 2px at 20px 30px, rgba(76, 175, 80, 0.3), transparent),
|
|
||||||
radial-gradient(2px 2px at 40px 70px, rgba(129, 199, 132, 0.2), transparent),
|
|
||||||
radial-gradient(1px 1px at 90px 40px, rgba(165, 214, 167, 0.3), transparent),
|
|
||||||
radial-gradient(1px 1px at 130px 80px, rgba(76, 175, 80, 0.2), transparent),
|
|
||||||
radial-gradient(2px 2px at 160px 30px, rgba(129, 199, 132, 0.3), transparent);
|
|
||||||
background-repeat: repeat;
|
|
||||||
background-size: 200px 100px;
|
|
||||||
animation: float 20s linear infinite;
|
|
||||||
pointer-events: none;
|
|
||||||
z-index: -1;
|
|
||||||
opacity: 0.6;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes float {
|
|
||||||
0% {
|
|
||||||
transform: translateY(0px);
|
|
||||||
}
|
|
||||||
50% {
|
|
||||||
transform: translateY(-10px);
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
transform: translateY(0px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 题目容器背景增强 */
|
|
||||||
.question-container {
|
|
||||||
background: rgba(255, 255, 255, 0.95);
|
|
||||||
backdrop-filter: blur(10px);
|
|
||||||
-webkit-backdrop-filter: blur(10px);
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
||||||
box-shadow:
|
|
||||||
0 8px 32px rgba(26, 77, 26, 0.1),
|
|
||||||
inset 0 1px 0 rgba(255, 255, 255, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 错误容器背景 */
|
|
||||||
.error-container {
|
|
||||||
background: rgba(255, 255, 255, 0.95);
|
|
||||||
backdrop-filter: blur(10px);
|
|
||||||
-webkit-backdrop-filter: blur(10px);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 结果容器背景 */
|
|
||||||
.result-container {
|
|
||||||
background: rgba(255, 255, 255, 0.9);
|
|
||||||
backdrop-filter: blur(5px);
|
|
||||||
-webkit-backdrop-filter: blur(5px);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 代码块背景 */
|
|
||||||
.code-block {
|
|
||||||
background: rgba(248, 249, 250, 0.9);
|
|
||||||
backdrop-filter: blur(5px);
|
|
||||||
-webkit-backdrop-filter: blur(5px);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 选项背景 */
|
|
||||||
.option {
|
|
||||||
background: rgba(255, 255, 255, 0.8);
|
|
||||||
backdrop-filter: blur(5px);
|
|
||||||
-webkit-backdrop-filter: blur(5px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.option:hover {
|
|
||||||
background: rgba(76, 175, 80, 0.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
.option.selected {
|
|
||||||
background: linear-gradient(135deg, rgba(76, 175, 80, 0.15), rgba(76, 175, 80, 0.08));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 按钮背景增强 */
|
|
||||||
.submit-btn {
|
|
||||||
background: linear-gradient(135deg, #4caf50, #45a049);
|
|
||||||
box-shadow: 0 4px 15px rgba(76, 175, 80, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.show-answer-btn {
|
|
||||||
background: linear-gradient(135deg, #2196f3, #1976d2);
|
|
||||||
box-shadow: 0 4px 15px rgba(33, 150, 243, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.retry-btn {
|
|
||||||
background: linear-gradient(135deg, #ff9800, #f57c00);
|
|
||||||
box-shadow: 0 4px 15px rgba(255, 152, 0, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.refresh-btn {
|
|
||||||
background: linear-gradient(135deg, #4caf50, #45a049);
|
|
||||||
box-shadow: 0 4px 15px rgba(76, 175, 80, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 移动端背景优化 */
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
body {
|
|
||||||
background-attachment: scroll;
|
|
||||||
}
|
|
||||||
|
|
||||||
body::after {
|
|
||||||
opacity: 0.4;
|
|
||||||
background-size: 150px 75px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.question-container {
|
|
||||||
backdrop-filter: blur(8px);
|
|
||||||
-webkit-backdrop-filter: blur(8px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 高对比度模式支持 */
|
|
||||||
@media (prefers-contrast: high) {
|
|
||||||
body {
|
|
||||||
background: #f0f8f0;
|
|
||||||
}
|
|
||||||
|
|
||||||
body::before,
|
|
||||||
body::after {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.question-container {
|
|
||||||
background: #ffffff;
|
|
||||||
border: 2px solid #4caf50;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 减少动画模式支持 */
|
|
||||||
@media (prefers-reduced-motion: reduce) {
|
|
||||||
body {
|
|
||||||
animation: none;
|
|
||||||
background: #e8f5e8;
|
|
||||||
}
|
|
||||||
|
|
||||||
body::after {
|
|
||||||
animation: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.refresh-btn:hover {
|
|
||||||
transform: scale(1.1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,597 +0,0 @@
|
|||||||
/* 基础样式重置 */
|
|
||||||
* {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
|
|
||||||
line-height: 1.6;
|
|
||||||
color: #2d5a27;
|
|
||||||
min-height: 100vh;
|
|
||||||
overflow-x: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 容器布局 */
|
|
||||||
.container {
|
|
||||||
max-width: 1200px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 20px;
|
|
||||||
min-height: 100vh;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 头部样式 */
|
|
||||||
.header {
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 40px;
|
|
||||||
padding: 30px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header h1 {
|
|
||||||
font-size: 2.5rem;
|
|
||||||
color: #1a4d1a;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
font-weight: 700;
|
|
||||||
text-shadow: 0 2px 4px rgba(26, 77, 26, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.subtitle {
|
|
||||||
font-size: 1.1rem;
|
|
||||||
color: #4a7c59;
|
|
||||||
opacity: 0.8;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 主内容区域 */
|
|
||||||
.main-content {
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: flex-start;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 加载动画 */
|
|
||||||
.loading {
|
|
||||||
text-align: center;
|
|
||||||
padding: 60px 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.spinner {
|
|
||||||
width: 50px;
|
|
||||||
height: 50px;
|
|
||||||
border: 4px solid #e8f5e8;
|
|
||||||
border-top: 4px solid #4caf50;
|
|
||||||
border-radius: 50%;
|
|
||||||
animation: spin 1s linear infinite;
|
|
||||||
margin: 0 auto 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes spin {
|
|
||||||
0% { transform: rotate(0deg); }
|
|
||||||
100% { transform: rotate(360deg); }
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading p {
|
|
||||||
color: #4a7c59;
|
|
||||||
font-size: 1.1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 题目容器 */
|
|
||||||
.question-container {
|
|
||||||
background: rgba(255, 255, 255, 0.95);
|
|
||||||
border-radius: 20px;
|
|
||||||
padding: 40px;
|
|
||||||
box-shadow: 0 10px 30px rgba(26, 77, 26, 0.1);
|
|
||||||
border: 2px solid rgba(76, 175, 80, 0.2);
|
|
||||||
width: 100%;
|
|
||||||
max-width: 800px;
|
|
||||||
backdrop-filter: blur(10px);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 题目头部 */
|
|
||||||
.question-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 30px;
|
|
||||||
padding-bottom: 15px;
|
|
||||||
border-bottom: 2px solid #e8f5e8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.question-id {
|
|
||||||
font-size: 1.1rem;
|
|
||||||
color: #4caf50;
|
|
||||||
font-weight: 600;
|
|
||||||
background: linear-gradient(135deg, #e8f5e8, #c8e6c9);
|
|
||||||
padding: 8px 16px;
|
|
||||||
border-radius: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.refresh-btn {
|
|
||||||
background: linear-gradient(135deg, #4caf50, #45a049);
|
|
||||||
border: none;
|
|
||||||
border-radius: 50%;
|
|
||||||
width: 45px;
|
|
||||||
height: 45px;
|
|
||||||
color: white;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.refresh-btn:hover {
|
|
||||||
transform: rotate(180deg) scale(1.1);
|
|
||||||
box-shadow: 0 5px 15px rgba(76, 175, 80, 0.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 题目文本 */
|
|
||||||
.question-text h2 {
|
|
||||||
font-size: 1.5rem;
|
|
||||||
color: #1a4d1a;
|
|
||||||
margin-bottom: 25px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 代码块 */
|
|
||||||
.code-block {
|
|
||||||
background: #f8f9fa;
|
|
||||||
border: 2px solid #e8f5e8;
|
|
||||||
border-radius: 12px;
|
|
||||||
margin: 25px 0;
|
|
||||||
overflow-x: auto;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.code-block pre {
|
|
||||||
margin: 0;
|
|
||||||
padding: 25px;
|
|
||||||
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
|
|
||||||
font-size: 0.95rem;
|
|
||||||
line-height: 1.5;
|
|
||||||
color: #2d5a27;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
word-wrap: break-word;
|
|
||||||
background: transparent !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.code-block code {
|
|
||||||
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
|
|
||||||
background: transparent !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 代码高亮自定义样式 - 丰富的语法高亮 */
|
|
||||||
.code-block .hljs {
|
|
||||||
background: transparent !important;
|
|
||||||
color: #333333 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* JavaScript 关键字 - 蓝色 */
|
|
||||||
.code-block .hljs-keyword {
|
|
||||||
color: #0066cc !important;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 字符串 - 绿色 */
|
|
||||||
.code-block .hljs-string {
|
|
||||||
color: #22aa22 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 数字 - 橙色 */
|
|
||||||
.code-block .hljs-number {
|
|
||||||
color: #ff6600 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 函数名 - 紫色 */
|
|
||||||
.code-block .hljs-function,
|
|
||||||
.code-block .hljs-title.function_ {
|
|
||||||
color: #9933cc !important;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 变量名 - 深蓝色 */
|
|
||||||
.code-block .hljs-variable,
|
|
||||||
.code-block .hljs-name {
|
|
||||||
color: #0066aa !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 注释 - 灰色 */
|
|
||||||
.code-block .hljs-comment {
|
|
||||||
color: #888888 !important;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 内置对象和方法 - 深紫色 */
|
|
||||||
.code-block .hljs-built_in {
|
|
||||||
color: #663399 !important;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 字面量 (true, false, null) - 红色 */
|
|
||||||
.code-block .hljs-literal {
|
|
||||||
color: #cc0000 !important;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 操作符 - 深灰色 */
|
|
||||||
.code-block .hljs-operator {
|
|
||||||
color: #666666 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 标点符号 - 深灰色 */
|
|
||||||
.code-block .hljs-punctuation {
|
|
||||||
color: #666666 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 属性名 - 深蓝色 */
|
|
||||||
.code-block .hljs-property,
|
|
||||||
.code-block .hljs-attr {
|
|
||||||
color: #0066aa !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 类名和构造函数 - 深绿色 */
|
|
||||||
.code-block .hljs-title.class_,
|
|
||||||
.code-block .hljs-title {
|
|
||||||
color: #228833 !important;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 参数 - 深蓝色 */
|
|
||||||
.code-block .hljs-params {
|
|
||||||
color: #0066aa !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 正则表达式 - 深红色 */
|
|
||||||
.code-block .hljs-regexp {
|
|
||||||
color: #aa0066 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 模板字符串 - 深绿色 */
|
|
||||||
.code-block .hljs-template-variable,
|
|
||||||
.code-block .hljs-template-tag {
|
|
||||||
color: #228833 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 选项容器 */
|
|
||||||
.options-container {
|
|
||||||
margin: 30px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.option {
|
|
||||||
background: rgba(255, 255, 255, 0.8);
|
|
||||||
border: 2px solid #e8f5e8;
|
|
||||||
border-radius: 12px;
|
|
||||||
padding: 15px 20px;
|
|
||||||
margin: 12px 0;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
font-size: 1rem;
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.option:hover {
|
|
||||||
border-color: #4caf50;
|
|
||||||
background: rgba(76, 175, 80, 0.05);
|
|
||||||
transform: translateX(5px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.option.selected {
|
|
||||||
border-color: #4caf50;
|
|
||||||
background: linear-gradient(135deg, rgba(76, 175, 80, 0.1), rgba(76, 175, 80, 0.05));
|
|
||||||
color: #1a4d1a;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.option.correct {
|
|
||||||
border-color: #4caf50;
|
|
||||||
background: linear-gradient(135deg, rgba(76, 175, 80, 0.2), rgba(76, 175, 80, 0.1));
|
|
||||||
color: #1a4d1a;
|
|
||||||
}
|
|
||||||
|
|
||||||
.option.incorrect {
|
|
||||||
border-color: #f44336;
|
|
||||||
background: linear-gradient(135deg, rgba(244, 67, 54, 0.1), rgba(244, 67, 54, 0.05));
|
|
||||||
color: #c62828;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 按钮样式 */
|
|
||||||
.action-buttons {
|
|
||||||
display: flex;
|
|
||||||
gap: 15px;
|
|
||||||
margin: 30px 0;
|
|
||||||
justify-content: center;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.submit-btn, .show-answer-btn, .retry-btn, .export-btn {
|
|
||||||
padding: 12px 30px;
|
|
||||||
border: none;
|
|
||||||
border-radius: 25px;
|
|
||||||
font-size: 1rem;
|
|
||||||
font-weight: 600;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
min-width: 120px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.submit-btn {
|
|
||||||
background: linear-gradient(135deg, #4caf50, #45a049);
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.submit-btn:hover:not(:disabled) {
|
|
||||||
transform: translateY(-2px);
|
|
||||||
box-shadow: 0 5px 15px rgba(76, 175, 80, 0.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
.submit-btn:disabled {
|
|
||||||
background: #cccccc;
|
|
||||||
cursor: not-allowed;
|
|
||||||
transform: none;
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.show-answer-btn {
|
|
||||||
background: linear-gradient(135deg, #2196f3, #1976d2);
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.show-answer-btn:hover {
|
|
||||||
transform: translateY(-2px);
|
|
||||||
box-shadow: 0 5px 15px rgba(33, 150, 243, 0.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
.retry-btn {
|
|
||||||
background: linear-gradient(135deg, #ff9800, #f57c00);
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.retry-btn:hover {
|
|
||||||
transform: translateY(-2px);
|
|
||||||
box-shadow: 0 5px 15px rgba(255, 152, 0, 0.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
.export-btn {
|
|
||||||
background: linear-gradient(135deg, #9c27b0, #7b1fa2);
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.export-btn:hover {
|
|
||||||
transform: translateY(-2px);
|
|
||||||
box-shadow: 0 5px 15px rgba(156, 39, 176, 0.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
.export-btn svg {
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 结果容器 */
|
|
||||||
.result-container {
|
|
||||||
margin-top: 30px;
|
|
||||||
padding: 25px;
|
|
||||||
background: rgba(255, 255, 255, 0.9);
|
|
||||||
border-radius: 15px;
|
|
||||||
border: 2px solid #e8f5e8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.result-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
padding-bottom: 15px;
|
|
||||||
border-bottom: 1px solid #e8f5e8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.result-status {
|
|
||||||
font-size: 1.2rem;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.result-status.correct {
|
|
||||||
color: #4caf50;
|
|
||||||
}
|
|
||||||
|
|
||||||
.result-status.incorrect {
|
|
||||||
color: #f44336;
|
|
||||||
}
|
|
||||||
|
|
||||||
.correct-answer {
|
|
||||||
font-weight: 600;
|
|
||||||
color: #4caf50;
|
|
||||||
background: rgba(76, 175, 80, 0.1);
|
|
||||||
padding: 5px 12px;
|
|
||||||
border-radius: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.explanation {
|
|
||||||
color: #2d5a27;
|
|
||||||
line-height: 1.7;
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.explanation pre {
|
|
||||||
background: #f8f9fa;
|
|
||||||
border: 1px solid #e8f5e8;
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 15px;
|
|
||||||
margin: 15px 0;
|
|
||||||
overflow-x: auto;
|
|
||||||
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 错误容器 */
|
|
||||||
.error-container {
|
|
||||||
text-align: center;
|
|
||||||
padding: 60px 20px;
|
|
||||||
background: rgba(255, 255, 255, 0.9);
|
|
||||||
border-radius: 20px;
|
|
||||||
border: 2px solid rgba(244, 67, 54, 0.2);
|
|
||||||
max-width: 500px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-icon {
|
|
||||||
font-size: 3rem;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-container h3 {
|
|
||||||
color: #f44336;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
font-size: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-container p {
|
|
||||||
color: #666;
|
|
||||||
margin-bottom: 25px;
|
|
||||||
font-size: 1.1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 底部 */
|
|
||||||
.footer {
|
|
||||||
text-align: center;
|
|
||||||
padding: 30px 0;
|
|
||||||
margin-top: 40px;
|
|
||||||
color: #4a7c59;
|
|
||||||
opacity: 0.7;
|
|
||||||
border-top: 1px solid rgba(76, 175, 80, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 平板端适配 (768px - 1024px) */
|
|
||||||
@media (max-width: 1024px) and (min-width: 768px) {
|
|
||||||
.container {
|
|
||||||
padding: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header h1 {
|
|
||||||
font-size: 2.2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.question-container {
|
|
||||||
padding: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-buttons {
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 手机端适配 (最大768px) */
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.container {
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header {
|
|
||||||
margin-bottom: 25px;
|
|
||||||
padding: 20px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header h1 {
|
|
||||||
font-size: 1.8rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.subtitle {
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.question-container {
|
|
||||||
padding: 20px;
|
|
||||||
border-radius: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.question-header {
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 15px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.question-text h2 {
|
|
||||||
font-size: 1.3rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.code-block pre {
|
|
||||||
padding: 15px;
|
|
||||||
font-size: 0.85rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.option {
|
|
||||||
padding: 12px 15px;
|
|
||||||
font-size: 0.95rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-buttons {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.submit-btn, .show-answer-btn, .retry-btn {
|
|
||||||
width: 100%;
|
|
||||||
max-width: 200px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.result-header {
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 10px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.explanation {
|
|
||||||
font-size: 0.95rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.explanation pre {
|
|
||||||
padding: 10px;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 小屏手机适配 (最大480px) */
|
|
||||||
@media (max-width: 480px) {
|
|
||||||
.container {
|
|
||||||
padding: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header h1 {
|
|
||||||
font-size: 1.6rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.question-container {
|
|
||||||
padding: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.question-id {
|
|
||||||
font-size: 1rem;
|
|
||||||
padding: 6px 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.refresh-btn {
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.code-block pre {
|
|
||||||
font-size: 0.8rem;
|
|
||||||
padding: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.option {
|
|
||||||
padding: 10px 12px;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.submit-btn, .show-answer-btn, .retry-btn {
|
|
||||||
padding: 10px 20px;
|
|
||||||
font-size: 0.95rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="zh-CN">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>随机JavaScript趣味题</title>
|
|
||||||
<link rel="stylesheet" href="css/style.css">
|
|
||||||
<link rel="stylesheet" href="css/background.css">
|
|
||||||
<link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/highlight.js/11.9.0/styles/github.min.css">
|
|
||||||
<script src="https://cdn.bootcdn.net/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
|
|
||||||
<script src="https://cdn.bootcdn.net/ajax/libs/highlight.js/11.9.0/languages/javascript.min.js"></script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="container">
|
|
||||||
<header class="header">
|
|
||||||
<h1>JavaScript趣味题</h1>
|
|
||||||
<p class="subtitle">测试你的JavaScript知识</p>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main class="main-content">
|
|
||||||
<div class="loading" id="loading">
|
|
||||||
<div class="spinner"></div>
|
|
||||||
<p>正在加载题目...</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="question-container" id="questionContainer" style="display: none;">
|
|
||||||
<div class="question-header">
|
|
||||||
<span class="question-id" id="questionId">题目 #1</span>
|
|
||||||
<button class="refresh-btn" id="refreshBtn" title="获取新题目">
|
|
||||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
||||||
<polyline points="23 4 23 10 17 10"></polyline>
|
|
||||||
<polyline points="1 20 1 14 7 14"></polyline>
|
|
||||||
<path d="M20.49 9A9 9 0 0 0 5.64 5.64L1 10m22 4l-4.64 4.36A9 9 0 0 1 3.51 15"></path>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="question-text" id="questionText">
|
|
||||||
<h2>输出是什么?</h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="code-block" id="codeBlock">
|
|
||||||
<pre><code id="codeContent" class="language-javascript"></code></pre>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="options-container" id="optionsContainer">
|
|
||||||
<!-- 选项将通过JavaScript动态生成 -->
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="action-buttons">
|
|
||||||
<button class="submit-btn" id="submitBtn" disabled>提交答案</button>
|
|
||||||
<button class="show-answer-btn" id="showAnswerBtn">查看答案</button>
|
|
||||||
<button class="export-btn" id="exportBtn" title="导出为Markdown文件">
|
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
||||||
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
|
|
||||||
<polyline points="7 10 12 15 17 10"></polyline>
|
|
||||||
<line x1="12" y1="15" x2="12" y2="3"></line>
|
|
||||||
</svg>
|
|
||||||
导出MD
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="result-container" id="resultContainer" style="display: none;">
|
|
||||||
<div class="result-header">
|
|
||||||
<span class="result-status" id="resultStatus"></span>
|
|
||||||
<span class="correct-answer" id="correctAnswer"></span>
|
|
||||||
</div>
|
|
||||||
<div class="explanation" id="explanation">
|
|
||||||
<!-- 解析内容 -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="error-container" id="errorContainer" style="display: none;">
|
|
||||||
<div class="error-icon">⚠️</div>
|
|
||||||
<h3>加载失败</h3>
|
|
||||||
<p id="errorMessage">网络连接异常,请稍后重试</p>
|
|
||||||
<button class="retry-btn" id="retryBtn">重新加载</button>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<footer class="footer">
|
|
||||||
<p>JavaScript趣味题集合</p>
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src="js/script.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,565 +0,0 @@
|
|||||||
// JavaScript趣味题应用
|
|
||||||
class JSQuizApp {
|
|
||||||
constructor() {
|
|
||||||
this.apiEndpoints = [
|
|
||||||
'https://60s-cf.viki.moe',
|
|
||||||
'https://60s.viki.moe',
|
|
||||||
'https://60s.b23.run',
|
|
||||||
'https://60s.114128.xyz',
|
|
||||||
'https://60s-cf.114128.xyz'
|
|
||||||
];
|
|
||||||
this.currentApiIndex = 0;
|
|
||||||
this.currentQuestion = null;
|
|
||||||
this.selectedOption = null;
|
|
||||||
this.isAnswered = false;
|
|
||||||
|
|
||||||
this.initElements();
|
|
||||||
this.bindEvents();
|
|
||||||
this.loadQuestion();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 初始化DOM元素
|
|
||||||
initElements() {
|
|
||||||
this.elements = {
|
|
||||||
loading: document.getElementById('loading'),
|
|
||||||
questionContainer: document.getElementById('questionContainer'),
|
|
||||||
errorContainer: document.getElementById('errorContainer'),
|
|
||||||
questionId: document.getElementById('questionId'),
|
|
||||||
questionText: document.getElementById('questionText'),
|
|
||||||
codeContent: document.getElementById('codeContent'),
|
|
||||||
optionsContainer: document.getElementById('optionsContainer'),
|
|
||||||
submitBtn: document.getElementById('submitBtn'),
|
|
||||||
showAnswerBtn: document.getElementById('showAnswerBtn'),
|
|
||||||
refreshBtn: document.getElementById('refreshBtn'),
|
|
||||||
retryBtn: document.getElementById('retryBtn'),
|
|
||||||
exportBtn: document.getElementById('exportBtn'),
|
|
||||||
resultContainer: document.getElementById('resultContainer'),
|
|
||||||
resultStatus: document.getElementById('resultStatus'),
|
|
||||||
correctAnswer: document.getElementById('correctAnswer'),
|
|
||||||
explanation: document.getElementById('explanation'),
|
|
||||||
errorMessage: document.getElementById('errorMessage')
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// 绑定事件
|
|
||||||
bindEvents() {
|
|
||||||
this.elements.submitBtn.addEventListener('click', () => this.submitAnswer());
|
|
||||||
this.elements.showAnswerBtn.addEventListener('click', () => this.showAnswer());
|
|
||||||
this.elements.refreshBtn.addEventListener('click', () => this.loadQuestion());
|
|
||||||
this.elements.retryBtn.addEventListener('click', () => this.loadQuestion());
|
|
||||||
this.elements.exportBtn.addEventListener('click', () => this.exportToMarkdown());
|
|
||||||
}
|
|
||||||
|
|
||||||
// 显示加载状态
|
|
||||||
showLoading() {
|
|
||||||
this.elements.loading.style.display = 'block';
|
|
||||||
this.elements.questionContainer.style.display = 'none';
|
|
||||||
this.elements.errorContainer.style.display = 'none';
|
|
||||||
}
|
|
||||||
|
|
||||||
// 显示题目
|
|
||||||
showQuestion() {
|
|
||||||
this.elements.loading.style.display = 'none';
|
|
||||||
this.elements.questionContainer.style.display = 'block';
|
|
||||||
this.elements.errorContainer.style.display = 'none';
|
|
||||||
}
|
|
||||||
|
|
||||||
// 显示错误
|
|
||||||
showError(message) {
|
|
||||||
this.elements.loading.style.display = 'none';
|
|
||||||
this.elements.questionContainer.style.display = 'none';
|
|
||||||
this.elements.errorContainer.style.display = 'block';
|
|
||||||
this.elements.errorMessage.textContent = message;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取当前API地址
|
|
||||||
getCurrentApiUrl() {
|
|
||||||
return `${this.apiEndpoints[this.currentApiIndex]}/v2/awesome-js`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 切换到下一个API
|
|
||||||
switchToNextApi() {
|
|
||||||
this.currentApiIndex = (this.currentApiIndex + 1) % this.apiEndpoints.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 加载题目
|
|
||||||
async loadQuestion() {
|
|
||||||
this.showLoading();
|
|
||||||
this.resetQuestion();
|
|
||||||
|
|
||||||
let attempts = 0;
|
|
||||||
const maxAttempts = this.apiEndpoints.length;
|
|
||||||
|
|
||||||
while (attempts < maxAttempts) {
|
|
||||||
try {
|
|
||||||
const response = await fetch(this.getCurrentApiUrl(), {
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
'Accept': 'application/json',
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
timeout: 10000
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
if (data.code === 200 && data.data) {
|
|
||||||
this.currentQuestion = data.data;
|
|
||||||
this.displayQuestion();
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
throw new Error(data.message || '数据格式错误');
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.warn(`API ${this.getCurrentApiUrl()} 请求失败:`, error.message);
|
|
||||||
attempts++;
|
|
||||||
|
|
||||||
if (attempts < maxAttempts) {
|
|
||||||
this.switchToNextApi();
|
|
||||||
console.log(`切换到备用API: ${this.getCurrentApiUrl()}`);
|
|
||||||
} else {
|
|
||||||
this.showError(`所有API接口都无法访问,请检查网络连接后重试。\n最后尝试的错误: ${error.message}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 重置题目状态
|
|
||||||
resetQuestion() {
|
|
||||||
this.selectedOption = null;
|
|
||||||
this.isAnswered = false;
|
|
||||||
this.elements.resultContainer.style.display = 'none';
|
|
||||||
this.elements.submitBtn.disabled = true;
|
|
||||||
this.elements.submitBtn.textContent = '提交答案';
|
|
||||||
this.elements.showAnswerBtn.style.display = 'inline-block';
|
|
||||||
|
|
||||||
// 清空选项容器,防止重复显示
|
|
||||||
this.elements.optionsContainer.innerHTML = '';
|
|
||||||
|
|
||||||
// 移除所有选项的事件监听器
|
|
||||||
const existingOptions = document.querySelectorAll('.option');
|
|
||||||
existingOptions.forEach(option => {
|
|
||||||
option.removeEventListener('click', this.selectOption);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 显示题目内容
|
|
||||||
displayQuestion() {
|
|
||||||
const question = this.currentQuestion;
|
|
||||||
|
|
||||||
console.log('显示题目:', question);
|
|
||||||
|
|
||||||
// 设置题目ID
|
|
||||||
this.elements.questionId.textContent = `题目 #${question.id}`;
|
|
||||||
|
|
||||||
// 设置题目文本
|
|
||||||
this.elements.questionText.innerHTML = `<h2>${this.escapeHtml(question.question)}</h2>`;
|
|
||||||
|
|
||||||
// 设置代码内容并应用语法高亮
|
|
||||||
this.elements.codeContent.textContent = question.code;
|
|
||||||
|
|
||||||
// 应用语法高亮
|
|
||||||
if (typeof hljs !== 'undefined') {
|
|
||||||
hljs.highlightElement(this.elements.codeContent);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 确保选项容器已清空
|
|
||||||
this.elements.optionsContainer.innerHTML = '';
|
|
||||||
|
|
||||||
// 生成选项
|
|
||||||
this.generateOptions(question.options);
|
|
||||||
|
|
||||||
this.showQuestion();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 生成选项
|
|
||||||
generateOptions(options) {
|
|
||||||
// 确保清空容器
|
|
||||||
this.elements.optionsContainer.innerHTML = '';
|
|
||||||
|
|
||||||
// 验证选项数据
|
|
||||||
if (!Array.isArray(options) || options.length === 0) {
|
|
||||||
console.error('选项数据无效:', options);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 移除可能存在的重复选项
|
|
||||||
const uniqueOptions = [...new Set(options)];
|
|
||||||
|
|
||||||
uniqueOptions.forEach((option, index) => {
|
|
||||||
const optionElement = document.createElement('div');
|
|
||||||
optionElement.className = 'option';
|
|
||||||
optionElement.textContent = option;
|
|
||||||
optionElement.dataset.index = index;
|
|
||||||
optionElement.dataset.value = option.charAt(0); // A, B, C, D
|
|
||||||
|
|
||||||
optionElement.addEventListener('click', () => this.selectOption(optionElement));
|
|
||||||
|
|
||||||
this.elements.optionsContainer.appendChild(optionElement);
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('生成选项:', uniqueOptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 选择选项
|
|
||||||
selectOption(optionElement) {
|
|
||||||
if (this.isAnswered) return;
|
|
||||||
|
|
||||||
// 移除之前的选中状态
|
|
||||||
document.querySelectorAll('.option.selected').forEach(el => {
|
|
||||||
el.classList.remove('selected');
|
|
||||||
});
|
|
||||||
|
|
||||||
// 设置当前选中
|
|
||||||
optionElement.classList.add('selected');
|
|
||||||
this.selectedOption = optionElement.dataset.value;
|
|
||||||
|
|
||||||
// 启用提交按钮
|
|
||||||
this.elements.submitBtn.disabled = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 提交答案
|
|
||||||
submitAnswer() {
|
|
||||||
if (!this.selectedOption || this.isAnswered) return;
|
|
||||||
|
|
||||||
this.isAnswered = true;
|
|
||||||
this.elements.submitBtn.disabled = true;
|
|
||||||
this.elements.submitBtn.textContent = '已提交';
|
|
||||||
this.elements.showAnswerBtn.style.display = 'none';
|
|
||||||
|
|
||||||
const isCorrect = this.selectedOption === this.currentQuestion.answer;
|
|
||||||
|
|
||||||
// 显示结果
|
|
||||||
this.showResult(isCorrect);
|
|
||||||
|
|
||||||
// 标记选项
|
|
||||||
this.markOptions();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 显示答案
|
|
||||||
showAnswer() {
|
|
||||||
this.isAnswered = true;
|
|
||||||
this.elements.submitBtn.disabled = true;
|
|
||||||
this.elements.submitBtn.textContent = '已显示答案';
|
|
||||||
this.elements.showAnswerBtn.style.display = 'none';
|
|
||||||
|
|
||||||
// 显示结果(不判断对错)
|
|
||||||
this.showResult(null);
|
|
||||||
|
|
||||||
// 标记正确答案
|
|
||||||
this.markCorrectAnswer();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 显示结果
|
|
||||||
showResult(isCorrect) {
|
|
||||||
const resultContainer = this.elements.resultContainer;
|
|
||||||
const resultStatus = this.elements.resultStatus;
|
|
||||||
const correctAnswer = this.elements.correctAnswer;
|
|
||||||
const explanation = this.elements.explanation;
|
|
||||||
|
|
||||||
// 设置结果状态
|
|
||||||
if (isCorrect === true) {
|
|
||||||
resultStatus.textContent = '✅ 回答正确!';
|
|
||||||
resultStatus.className = 'result-status correct';
|
|
||||||
} else if (isCorrect === false) {
|
|
||||||
resultStatus.textContent = '❌ 回答错误';
|
|
||||||
resultStatus.className = 'result-status incorrect';
|
|
||||||
} else {
|
|
||||||
resultStatus.textContent = '💡 答案解析';
|
|
||||||
resultStatus.className = 'result-status';
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置正确答案
|
|
||||||
correctAnswer.textContent = `正确答案: ${this.currentQuestion.answer}`;
|
|
||||||
|
|
||||||
// 设置解析内容
|
|
||||||
explanation.innerHTML = this.formatExplanation(this.currentQuestion.explanation);
|
|
||||||
|
|
||||||
// 显示结果容器
|
|
||||||
resultContainer.style.display = 'block';
|
|
||||||
|
|
||||||
// 滚动到结果区域
|
|
||||||
setTimeout(() => {
|
|
||||||
resultContainer.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
|
||||||
}, 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 标记选项
|
|
||||||
markOptions() {
|
|
||||||
const options = document.querySelectorAll('.option');
|
|
||||||
const correctAnswer = this.currentQuestion.answer;
|
|
||||||
|
|
||||||
options.forEach(option => {
|
|
||||||
const optionValue = option.dataset.value;
|
|
||||||
|
|
||||||
if (optionValue === correctAnswer) {
|
|
||||||
option.classList.add('correct');
|
|
||||||
} else if (option.classList.contains('selected')) {
|
|
||||||
option.classList.add('incorrect');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 禁用点击
|
|
||||||
option.style.pointerEvents = 'none';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 标记正确答案
|
|
||||||
markCorrectAnswer() {
|
|
||||||
const options = document.querySelectorAll('.option');
|
|
||||||
const correctAnswer = this.currentQuestion.answer;
|
|
||||||
|
|
||||||
options.forEach(option => {
|
|
||||||
const optionValue = option.dataset.value;
|
|
||||||
|
|
||||||
if (optionValue === correctAnswer) {
|
|
||||||
option.classList.add('correct');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 禁用点击
|
|
||||||
option.style.pointerEvents = 'none';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 格式化解析内容
|
|
||||||
formatExplanation(explanation) {
|
|
||||||
// 转义HTML
|
|
||||||
let formatted = this.escapeHtml(explanation);
|
|
||||||
|
|
||||||
// 处理代码块
|
|
||||||
formatted = formatted.replace(/```js\n([\s\S]*?)\n```/g, '<pre><code>$1</code></pre>');
|
|
||||||
formatted = formatted.replace(/```javascript\n([\s\S]*?)\n```/g, '<pre><code>$1</code></pre>');
|
|
||||||
formatted = formatted.replace(/```([\s\S]*?)```/g, '<pre><code>$1</code></pre>');
|
|
||||||
|
|
||||||
// 处理行内代码
|
|
||||||
formatted = formatted.replace(/`([^`]+)`/g, '<code style="background: #f0f0f0; padding: 2px 4px; border-radius: 3px; font-family: monospace;">$1</code>');
|
|
||||||
|
|
||||||
// 处理换行
|
|
||||||
formatted = formatted.replace(/\n\n/g, '</p><p>');
|
|
||||||
formatted = formatted.replace(/\n/g, '<br>');
|
|
||||||
|
|
||||||
// 包装段落
|
|
||||||
if (!formatted.includes('<p>')) {
|
|
||||||
formatted = '<p>' + formatted + '</p>';
|
|
||||||
}
|
|
||||||
|
|
||||||
return formatted;
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTML转义
|
|
||||||
escapeHtml(text) {
|
|
||||||
const div = document.createElement('div');
|
|
||||||
div.textContent = text;
|
|
||||||
return div.innerHTML;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 导出为Markdown
|
|
||||||
exportToMarkdown() {
|
|
||||||
if (!this.currentQuestion) {
|
|
||||||
alert('请先加载题目后再导出!');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const question = this.currentQuestion;
|
|
||||||
const timestamp = new Date().toLocaleString('zh-CN');
|
|
||||||
|
|
||||||
// 构建Markdown内容
|
|
||||||
let markdown = `# JavaScript趣味题 #${question.id}\n\n`;
|
|
||||||
markdown += `> 导出时间: ${timestamp}\n\n`;
|
|
||||||
|
|
||||||
// 题目部分
|
|
||||||
markdown += `## 题目\n\n`;
|
|
||||||
markdown += `${question.question}\n\n`;
|
|
||||||
|
|
||||||
// 代码部分
|
|
||||||
markdown += `## 代码\n\n`;
|
|
||||||
markdown += `\`\`\`javascript\n${question.code}\n\`\`\`\n\n`;
|
|
||||||
|
|
||||||
// 选项部分
|
|
||||||
markdown += `## 选项\n\n`;
|
|
||||||
question.options.forEach((option, index) => {
|
|
||||||
const letter = String.fromCharCode(65 + index); // A, B, C, D
|
|
||||||
const isCorrect = letter === question.answer;
|
|
||||||
markdown += `${letter}. ${option}${isCorrect ? ' ✅' : ''}\n`;
|
|
||||||
});
|
|
||||||
markdown += `\n`;
|
|
||||||
|
|
||||||
// 答案部分
|
|
||||||
markdown += `## 正确答案\n\n`;
|
|
||||||
markdown += `**${question.answer}**\n\n`;
|
|
||||||
|
|
||||||
// 解析部分
|
|
||||||
markdown += `## 答案解析\n\n`;
|
|
||||||
// 清理解析内容中的HTML标签,转换为Markdown格式
|
|
||||||
let explanation = question.explanation;
|
|
||||||
explanation = explanation.replace(/<br\s*\/?>/gi, '\n');
|
|
||||||
explanation = explanation.replace(/<p>/gi, '\n');
|
|
||||||
explanation = explanation.replace(/<\/p>/gi, '\n');
|
|
||||||
explanation = explanation.replace(/<code[^>]*>/gi, '`');
|
|
||||||
explanation = explanation.replace(/<\/code>/gi, '`');
|
|
||||||
explanation = explanation.replace(/<pre><code>/gi, '\n```javascript\n');
|
|
||||||
explanation = explanation.replace(/<\/code><\/pre>/gi, '\n```\n');
|
|
||||||
explanation = explanation.replace(/<[^>]*>/g, ''); // 移除其他HTML标签
|
|
||||||
explanation = explanation.replace(/\n\s*\n/g, '\n\n'); // 清理多余空行
|
|
||||||
markdown += explanation.trim() + '\n\n';
|
|
||||||
|
|
||||||
// 添加页脚
|
|
||||||
markdown += `---\n\n`;
|
|
||||||
markdown += `*本题目来源于JavaScript趣味题集合*\n`;
|
|
||||||
markdown += `*导出工具: JavaScript趣味题网页版*\n`;
|
|
||||||
|
|
||||||
// 创建下载
|
|
||||||
this.downloadMarkdown(markdown, `JavaScript趣味题_${question.id}_${new Date().getTime()}.md`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 下载Markdown文件
|
|
||||||
downloadMarkdown(content, filename) {
|
|
||||||
const blob = new Blob([content], { type: 'text/markdown;charset=utf-8' });
|
|
||||||
const url = URL.createObjectURL(blob);
|
|
||||||
|
|
||||||
const link = document.createElement('a');
|
|
||||||
link.href = url;
|
|
||||||
link.download = filename;
|
|
||||||
link.style.display = 'none';
|
|
||||||
|
|
||||||
document.body.appendChild(link);
|
|
||||||
link.click();
|
|
||||||
document.body.removeChild(link);
|
|
||||||
|
|
||||||
// 清理URL对象
|
|
||||||
setTimeout(() => {
|
|
||||||
URL.revokeObjectURL(url);
|
|
||||||
}, 100);
|
|
||||||
|
|
||||||
// 显示成功提示
|
|
||||||
this.showExportSuccess(filename);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 显示导出成功提示
|
|
||||||
showExportSuccess(filename) {
|
|
||||||
// 创建临时提示元素
|
|
||||||
const toast = document.createElement('div');
|
|
||||||
toast.style.cssText = `
|
|
||||||
position: fixed;
|
|
||||||
top: 20px;
|
|
||||||
right: 20px;
|
|
||||||
background: linear-gradient(135deg, #4caf50, #45a049);
|
|
||||||
color: white;
|
|
||||||
padding: 15px 20px;
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: 0 4px 12px rgba(76, 175, 80, 0.3);
|
|
||||||
z-index: 10000;
|
|
||||||
font-size: 14px;
|
|
||||||
max-width: 300px;
|
|
||||||
word-wrap: break-word;
|
|
||||||
animation: slideInRight 0.3s ease-out;
|
|
||||||
`;
|
|
||||||
|
|
||||||
toast.innerHTML = `
|
|
||||||
<div style="display: flex; align-items: center; gap: 8px;">
|
|
||||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
||||||
<polyline points="20 6 9 17 4 12"></polyline>
|
|
||||||
</svg>
|
|
||||||
<div>
|
|
||||||
<div style="font-weight: 600;">导出成功!</div>
|
|
||||||
<div style="font-size: 12px; opacity: 0.9; margin-top: 2px;">${filename}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
// 添加动画样式
|
|
||||||
const style = document.createElement('style');
|
|
||||||
style.textContent = `
|
|
||||||
@keyframes slideInRight {
|
|
||||||
from {
|
|
||||||
transform: translateX(100%);
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
transform: translateX(0);
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@keyframes slideOutRight {
|
|
||||||
from {
|
|
||||||
transform: translateX(0);
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
transform: translateX(100%);
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
document.head.appendChild(style);
|
|
||||||
|
|
||||||
document.body.appendChild(toast);
|
|
||||||
|
|
||||||
// 3秒后自动消失
|
|
||||||
setTimeout(() => {
|
|
||||||
toast.style.animation = 'slideOutRight 0.3s ease-in';
|
|
||||||
setTimeout(() => {
|
|
||||||
if (toast.parentNode) {
|
|
||||||
document.body.removeChild(toast);
|
|
||||||
}
|
|
||||||
if (style.parentNode) {
|
|
||||||
document.head.removeChild(style);
|
|
||||||
}
|
|
||||||
}, 300);
|
|
||||||
}, 3000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 页面加载完成后初始化应用
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
|
||||||
new JSQuizApp();
|
|
||||||
});
|
|
||||||
|
|
||||||
// 添加键盘快捷键支持
|
|
||||||
document.addEventListener('keydown', (e) => {
|
|
||||||
// 按R键刷新题目
|
|
||||||
if (e.key.toLowerCase() === 'r' && !e.ctrlKey && !e.metaKey) {
|
|
||||||
const refreshBtn = document.getElementById('refreshBtn');
|
|
||||||
if (refreshBtn && !document.querySelector('.loading').style.display !== 'none') {
|
|
||||||
refreshBtn.click();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 按数字键1-4选择选项
|
|
||||||
if (['1', '2', '3', '4'].includes(e.key)) {
|
|
||||||
const options = document.querySelectorAll('.option');
|
|
||||||
const index = parseInt(e.key) - 1;
|
|
||||||
if (options[index] && !options[index].style.pointerEvents) {
|
|
||||||
options[index].click();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 按Enter键提交答案
|
|
||||||
if (e.key === 'Enter') {
|
|
||||||
const submitBtn = document.getElementById('submitBtn');
|
|
||||||
if (submitBtn && !submitBtn.disabled) {
|
|
||||||
submitBtn.click();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 添加触摸设备支持
|
|
||||||
if ('ontouchstart' in window) {
|
|
||||||
document.addEventListener('touchstart', () => {}, { passive: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加网络状态监听
|
|
||||||
if ('navigator' in window && 'onLine' in navigator) {
|
|
||||||
window.addEventListener('online', () => {
|
|
||||||
console.log('网络连接已恢复');
|
|
||||||
});
|
|
||||||
|
|
||||||
window.addEventListener('offline', () => {
|
|
||||||
console.log('网络连接已断开');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
{
|
|
||||||
"code": 200,
|
|
||||||
"message": "获取成功。数据来自官方/权威源头,以确保稳定与实时。开源地址 https://github.com/vikiboss/60s,反馈群 595941841",
|
|
||||||
"data": {
|
|
||||||
"id": 11,
|
|
||||||
"question": "输出是什么?",
|
|
||||||
"code": "function Person(firstName, lastName) {\n this.firstName = firstName;\n this.lastName = lastName;\n}\n\nconst member = new Person(\"Lydia\", \"Hallie\");\nPerson.getFullName = function () {\n return `${this.firstName} ${this.lastName}`;\n}\n\nconsole.log(member.getFullName());",
|
|
||||||
"options": [
|
|
||||||
"A: `TypeError`",
|
|
||||||
"B: `SyntaxError`",
|
|
||||||
"C: `Lydia Hallie`",
|
|
||||||
"D: `undefined` `undefined`"
|
|
||||||
],
|
|
||||||
"answer": "A",
|
|
||||||
"explanation": "你不能像常规对象那样,给构造函数添加属性。如果你想一次性给所有实例添加特性,你应该使用原型。因此本例中,使用如下方式:\n\n```js\nPerson.prototype.getFullName = function () {\n return `${this.firstName} ${this.lastName}`;\n}\n```\n\n这才会使 `member.getFullName()` 起作用。为什么这么做有益的?假设我们将这个方法添加到构造函数本身里。也许不是每个 `Person` 实例都需要这个方法。这将浪费大量内存空间,因为它们仍然具有该属性,这将占用每个实例的内存空间。相反,如果我们只将它添加到原型中,那么它只存在于内存中的一个位置,但是所有实例都可以访问它!"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
/* 背景样式文件 */
|
|
||||||
body {
|
|
||||||
background: linear-gradient(135deg, #a8e6cf 0%, #dcedc1 25%, #ffd3a5 50%, #a8e6cf 75%, #88d8a3 100%);
|
|
||||||
background-size: 400% 400%;
|
|
||||||
animation: gradientShift 15s ease infinite;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 背景动画 */
|
|
||||||
@keyframes gradientShift {
|
|
||||||
0% {
|
|
||||||
background-position: 0% 50%;
|
|
||||||
}
|
|
||||||
50% {
|
|
||||||
background-position: 100% 50%;
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
background-position: 0% 50%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 背景装饰元素 */
|
|
||||||
body::before {
|
|
||||||
content: '';
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background-image:
|
|
||||||
radial-gradient(circle at 20% 80%, rgba(39, 174, 96, 0.1) 0%, transparent 50%),
|
|
||||||
radial-gradient(circle at 80% 20%, rgba(46, 204, 113, 0.1) 0%, transparent 50%),
|
|
||||||
radial-gradient(circle at 40% 40%, rgba(26, 188, 156, 0.05) 0%, transparent 50%);
|
|
||||||
pointer-events: none;
|
|
||||||
z-index: -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 浮动装饰圆点 */
|
|
||||||
body::after {
|
|
||||||
content: '';
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background-image:
|
|
||||||
radial-gradient(2px 2px at 20px 30px, rgba(39, 174, 96, 0.3), transparent),
|
|
||||||
radial-gradient(2px 2px at 40px 70px, rgba(46, 204, 113, 0.2), transparent),
|
|
||||||
radial-gradient(1px 1px at 90px 40px, rgba(26, 188, 156, 0.3), transparent),
|
|
||||||
radial-gradient(1px 1px at 130px 80px, rgba(39, 174, 96, 0.2), transparent),
|
|
||||||
radial-gradient(2px 2px at 160px 30px, rgba(46, 204, 113, 0.3), transparent);
|
|
||||||
background-repeat: repeat;
|
|
||||||
background-size: 200px 100px;
|
|
||||||
animation: floatDots 20s linear infinite;
|
|
||||||
pointer-events: none;
|
|
||||||
z-index: -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes floatDots {
|
|
||||||
0% {
|
|
||||||
transform: translateY(0px);
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
transform: translateY(-100px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 响应式背景调整 */
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
body::after {
|
|
||||||
background-size: 150px 75px;
|
|
||||||
animation-duration: 25s;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 480px) {
|
|
||||||
body::after {
|
|
||||||
background-size: 100px 50px;
|
|
||||||
animation-duration: 30s;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,339 +0,0 @@
|
|||||||
/* 基础样式重置 */
|
|
||||||
* {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
font-family: 'Microsoft YaHei', 'PingFang SC', 'Helvetica Neue', Arial, sans-serif;
|
|
||||||
line-height: 1.6;
|
|
||||||
color: #2c3e50;
|
|
||||||
min-height: 100vh;
|
|
||||||
overflow-x: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 容器布局 */
|
|
||||||
.container {
|
|
||||||
min-height: 100vh;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
max-width: 1200px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 头部样式 */
|
|
||||||
.header {
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 40px;
|
|
||||||
padding: 30px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
font-size: 2.5rem;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #27ae60;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
text-shadow: 0 2px 4px rgba(39, 174, 96, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.subtitle {
|
|
||||||
font-size: 1.1rem;
|
|
||||||
color: #7f8c8d;
|
|
||||||
font-weight: 400;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 主要内容区域 */
|
|
||||||
.main {
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content-card {
|
|
||||||
background: rgba(255, 255, 255, 0.95);
|
|
||||||
border-radius: 20px;
|
|
||||||
padding: 40px;
|
|
||||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
|
|
||||||
backdrop-filter: blur(10px);
|
|
||||||
border: 1px solid rgba(39, 174, 96, 0.1);
|
|
||||||
width: 100%;
|
|
||||||
max-width: 600px;
|
|
||||||
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content-card:hover {
|
|
||||||
transform: translateY(-5px);
|
|
||||||
box-shadow: 0 15px 40px rgba(0, 0, 0, 0.15);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* KFC文案内容 */
|
|
||||||
.kfc-content {
|
|
||||||
min-height: 200px;
|
|
||||||
padding: 30px;
|
|
||||||
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
|
|
||||||
border-radius: 15px;
|
|
||||||
border-left: 5px solid #27ae60;
|
|
||||||
margin-bottom: 30px;
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.kfc-content::before {
|
|
||||||
content: '"';
|
|
||||||
position: absolute;
|
|
||||||
top: 10px;
|
|
||||||
left: 15px;
|
|
||||||
font-size: 3rem;
|
|
||||||
color: #27ae60;
|
|
||||||
opacity: 0.3;
|
|
||||||
font-family: serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
.kfc-content p {
|
|
||||||
font-size: 1.1rem;
|
|
||||||
line-height: 1.8;
|
|
||||||
color: #2c3e50;
|
|
||||||
margin-left: 20px;
|
|
||||||
position: relative;
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading-text {
|
|
||||||
text-align: center;
|
|
||||||
color: #7f8c8d;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 按钮组 */
|
|
||||||
.button-group {
|
|
||||||
display: flex;
|
|
||||||
gap: 15px;
|
|
||||||
justify-content: center;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.generate-btn, .copy-btn {
|
|
||||||
padding: 15px 30px;
|
|
||||||
border: none;
|
|
||||||
border-radius: 50px;
|
|
||||||
font-size: 1rem;
|
|
||||||
font-weight: 600;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.generate-btn {
|
|
||||||
background: linear-gradient(135deg, #27ae60 0%, #2ecc71 100%);
|
|
||||||
color: white;
|
|
||||||
box-shadow: 0 4px 15px rgba(39, 174, 96, 0.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
.generate-btn:hover {
|
|
||||||
transform: translateY(-2px);
|
|
||||||
box-shadow: 0 6px 20px rgba(39, 174, 96, 0.4);
|
|
||||||
}
|
|
||||||
|
|
||||||
.generate-btn:active {
|
|
||||||
transform: translateY(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
.generate-btn:disabled {
|
|
||||||
opacity: 0.7;
|
|
||||||
cursor: not-allowed;
|
|
||||||
transform: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.copy-btn {
|
|
||||||
background: linear-gradient(135deg, #3498db 0%, #5dade2 100%);
|
|
||||||
color: white;
|
|
||||||
box-shadow: 0 4px 15px rgba(52, 152, 219, 0.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
.copy-btn:hover {
|
|
||||||
transform: translateY(-2px);
|
|
||||||
box-shadow: 0 6px 20px rgba(52, 152, 219, 0.4);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 编号信息 */
|
|
||||||
.index-info {
|
|
||||||
text-align: center;
|
|
||||||
padding: 10px;
|
|
||||||
background: rgba(39, 174, 96, 0.1);
|
|
||||||
border-radius: 10px;
|
|
||||||
border: 1px solid rgba(39, 174, 96, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.index-text {
|
|
||||||
color: #27ae60;
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
#indexNumber {
|
|
||||||
color: #2c3e50;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 底部 */
|
|
||||||
.footer {
|
|
||||||
text-align: center;
|
|
||||||
padding: 20px 0;
|
|
||||||
color: #7f8c8d;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
margin-top: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 提示框 */
|
|
||||||
.toast {
|
|
||||||
position: fixed;
|
|
||||||
top: 20px;
|
|
||||||
right: 20px;
|
|
||||||
background: #27ae60;
|
|
||||||
color: white;
|
|
||||||
padding: 15px 25px;
|
|
||||||
border-radius: 10px;
|
|
||||||
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
|
|
||||||
transform: translateX(400px);
|
|
||||||
transition: transform 0.3s ease;
|
|
||||||
z-index: 1000;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toast.show {
|
|
||||||
transform: translateX(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 平板端适配 (768px - 1024px) */
|
|
||||||
@media (max-width: 1024px) and (min-width: 768px) {
|
|
||||||
.container {
|
|
||||||
padding: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
font-size: 2.2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content-card {
|
|
||||||
padding: 35px;
|
|
||||||
max-width: 550px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.kfc-content {
|
|
||||||
padding: 25px;
|
|
||||||
min-height: 180px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-group {
|
|
||||||
flex-direction: row;
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.generate-btn, .copy-btn {
|
|
||||||
padding: 12px 25px;
|
|
||||||
font-size: 0.95rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 手机端适配 (最大768px) */
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.container {
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header {
|
|
||||||
margin-bottom: 30px;
|
|
||||||
padding: 20px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
font-size: 1.8rem;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.subtitle {
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content-card {
|
|
||||||
padding: 25px;
|
|
||||||
margin: 0 5px;
|
|
||||||
border-radius: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.kfc-content {
|
|
||||||
padding: 20px;
|
|
||||||
min-height: 150px;
|
|
||||||
margin-bottom: 25px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.kfc-content::before {
|
|
||||||
font-size: 2.5rem;
|
|
||||||
top: 5px;
|
|
||||||
left: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.kfc-content p {
|
|
||||||
font-size: 1rem;
|
|
||||||
line-height: 1.7;
|
|
||||||
margin-left: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-group {
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.generate-btn, .copy-btn {
|
|
||||||
padding: 12px 20px;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer {
|
|
||||||
font-size: 0.8rem;
|
|
||||||
margin-top: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toast {
|
|
||||||
right: 10px;
|
|
||||||
left: 10px;
|
|
||||||
transform: translateY(-100px);
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toast.show {
|
|
||||||
transform: translateY(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 小屏手机适配 (最大480px) */
|
|
||||||
@media (max-width: 480px) {
|
|
||||||
.title {
|
|
||||||
font-size: 1.6rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content-card {
|
|
||||||
padding: 20px;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.kfc-content {
|
|
||||||
padding: 15px;
|
|
||||||
min-height: 120px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.kfc-content p {
|
|
||||||
font-size: 0.95rem;
|
|
||||||
margin-left: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.generate-btn, .copy-btn {
|
|
||||||
padding: 10px 15px;
|
|
||||||
font-size: 0.85rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="zh-CN">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>随机KFC文案生成器</title>
|
|
||||||
<link rel="stylesheet" href="css/style.css">
|
|
||||||
<link rel="stylesheet" href="css/background.css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="container">
|
|
||||||
<header class="header">
|
|
||||||
<h1 class="title">🍗 随机KFC文案生成器</h1>
|
|
||||||
<p class="subtitle">疯狂星期四,文案来一套!</p>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main class="main">
|
|
||||||
<div class="content-card">
|
|
||||||
<div class="kfc-content" id="kfcContent">
|
|
||||||
<p class="loading-text">点击按钮获取随机KFC文案...</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="button-group">
|
|
||||||
<button class="generate-btn" id="generateBtn">
|
|
||||||
<span class="btn-text">生成文案</span>
|
|
||||||
<span class="btn-loading" style="display: none;">生成中...</span>
|
|
||||||
</button>
|
|
||||||
<button class="copy-btn" id="copyBtn" style="display: none;">复制文案</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="index-info" id="indexInfo" style="display: none;">
|
|
||||||
<span class="index-text">文案编号: <span id="indexNumber"></span></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<footer class="footer">
|
|
||||||
<p>© 2024 KFC文案生成器 | 让每个星期四都疯狂起来</p>
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="toast" id="toast"></div>
|
|
||||||
|
|
||||||
<script src="js/main.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,240 +0,0 @@
|
|||||||
// KFC文案生成器主要功能
|
|
||||||
class KFCGenerator {
|
|
||||||
constructor() {
|
|
||||||
this.apiEndpoints = [];
|
|
||||||
this.currentApiIndex = 0;
|
|
||||||
this.isLoading = false;
|
|
||||||
|
|
||||||
this.init();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 初始化
|
|
||||||
async init() {
|
|
||||||
await this.loadApiEndpoints();
|
|
||||||
this.bindEvents();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 加载API接口列表
|
|
||||||
async loadApiEndpoints() {
|
|
||||||
try {
|
|
||||||
const response = await fetch('./接口集合.json');
|
|
||||||
this.apiEndpoints = await response.json();
|
|
||||||
} catch (error) {
|
|
||||||
console.error('加载API接口列表失败:', error);
|
|
||||||
this.showToast('加载接口配置失败', 'error');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 绑定事件
|
|
||||||
bindEvents() {
|
|
||||||
const generateBtn = document.getElementById('generateBtn');
|
|
||||||
const copyBtn = document.getElementById('copyBtn');
|
|
||||||
|
|
||||||
generateBtn.addEventListener('click', () => this.generateKFC());
|
|
||||||
copyBtn.addEventListener('click', () => this.copyContent());
|
|
||||||
}
|
|
||||||
|
|
||||||
// 生成KFC文案
|
|
||||||
async generateKFC() {
|
|
||||||
if (this.isLoading) return;
|
|
||||||
|
|
||||||
this.setLoadingState(true);
|
|
||||||
|
|
||||||
let success = false;
|
|
||||||
let attempts = 0;
|
|
||||||
const maxAttempts = this.apiEndpoints.length;
|
|
||||||
|
|
||||||
while (!success && attempts < maxAttempts) {
|
|
||||||
try {
|
|
||||||
const apiUrl = this.apiEndpoints[this.currentApiIndex];
|
|
||||||
const data = await this.fetchKFCData(apiUrl);
|
|
||||||
|
|
||||||
if (data && data.code === 200 && data.data && data.data.kfc) {
|
|
||||||
this.displayKFC(data.data);
|
|
||||||
success = true;
|
|
||||||
} else {
|
|
||||||
throw new Error('API返回数据格式错误');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`API ${this.currentApiIndex + 1} 请求失败:`, error);
|
|
||||||
this.currentApiIndex = (this.currentApiIndex + 1) % this.apiEndpoints.length;
|
|
||||||
attempts++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!success) {
|
|
||||||
this.showError('所有API接口都无法访问,请稍后重试');
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setLoadingState(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 请求KFC数据
|
|
||||||
async fetchKFCData(apiUrl) {
|
|
||||||
const controller = new AbortController();
|
|
||||||
const timeoutId = setTimeout(() => controller.abort(), 10000); // 10秒超时
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch(`${apiUrl}/v2/kfc`, {
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
'Accept': 'application/json',
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
signal: controller.signal
|
|
||||||
});
|
|
||||||
|
|
||||||
clearTimeout(timeoutId);
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return await response.json();
|
|
||||||
} catch (error) {
|
|
||||||
clearTimeout(timeoutId);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 显示KFC文案
|
|
||||||
displayKFC(data) {
|
|
||||||
const contentElement = document.getElementById('kfcContent');
|
|
||||||
const indexElement = document.getElementById('indexNumber');
|
|
||||||
const indexInfo = document.getElementById('indexInfo');
|
|
||||||
const copyBtn = document.getElementById('copyBtn');
|
|
||||||
|
|
||||||
// 显示文案内容
|
|
||||||
contentElement.innerHTML = `<p>${this.escapeHtml(data.kfc)}</p>`;
|
|
||||||
|
|
||||||
// 显示编号信息
|
|
||||||
if (data.index) {
|
|
||||||
indexElement.textContent = data.index;
|
|
||||||
indexInfo.style.display = 'block';
|
|
||||||
} else {
|
|
||||||
indexInfo.style.display = 'none';
|
|
||||||
}
|
|
||||||
|
|
||||||
// 显示复制按钮
|
|
||||||
copyBtn.style.display = 'inline-block';
|
|
||||||
|
|
||||||
// 添加显示动画
|
|
||||||
contentElement.style.opacity = '0';
|
|
||||||
contentElement.style.transform = 'translateY(20px)';
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
contentElement.style.transition = 'all 0.5s ease';
|
|
||||||
contentElement.style.opacity = '1';
|
|
||||||
contentElement.style.transform = 'translateY(0)';
|
|
||||||
}, 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 显示错误信息
|
|
||||||
showError(message) {
|
|
||||||
const contentElement = document.getElementById('kfcContent');
|
|
||||||
contentElement.innerHTML = `<p class="loading-text" style="color: #e74c3c;">${this.escapeHtml(message)}</p>`;
|
|
||||||
|
|
||||||
const copyBtn = document.getElementById('copyBtn');
|
|
||||||
const indexInfo = document.getElementById('indexInfo');
|
|
||||||
copyBtn.style.display = 'none';
|
|
||||||
indexInfo.style.display = 'none';
|
|
||||||
}
|
|
||||||
|
|
||||||
// 复制文案内容
|
|
||||||
async copyContent() {
|
|
||||||
const contentElement = document.getElementById('kfcContent');
|
|
||||||
const textContent = contentElement.querySelector('p')?.textContent;
|
|
||||||
|
|
||||||
if (!textContent || textContent.includes('点击按钮获取') || textContent.includes('失败')) {
|
|
||||||
this.showToast('没有可复制的内容', 'error');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (navigator.clipboard && window.isSecureContext) {
|
|
||||||
await navigator.clipboard.writeText(textContent);
|
|
||||||
} else {
|
|
||||||
// 降级方案
|
|
||||||
const textArea = document.createElement('textarea');
|
|
||||||
textArea.value = textContent;
|
|
||||||
textArea.style.position = 'fixed';
|
|
||||||
textArea.style.left = '-999999px';
|
|
||||||
textArea.style.top = '-999999px';
|
|
||||||
document.body.appendChild(textArea);
|
|
||||||
textArea.focus();
|
|
||||||
textArea.select();
|
|
||||||
document.execCommand('copy');
|
|
||||||
textArea.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.showToast('文案已复制到剪贴板', 'success');
|
|
||||||
} catch (error) {
|
|
||||||
console.error('复制失败:', error);
|
|
||||||
this.showToast('复制失败,请手动选择复制', 'error');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置加载状态
|
|
||||||
setLoadingState(loading) {
|
|
||||||
this.isLoading = loading;
|
|
||||||
const generateBtn = document.getElementById('generateBtn');
|
|
||||||
const btnText = generateBtn.querySelector('.btn-text');
|
|
||||||
const btnLoading = generateBtn.querySelector('.btn-loading');
|
|
||||||
|
|
||||||
if (loading) {
|
|
||||||
generateBtn.disabled = true;
|
|
||||||
btnText.style.display = 'none';
|
|
||||||
btnLoading.style.display = 'inline';
|
|
||||||
} else {
|
|
||||||
generateBtn.disabled = false;
|
|
||||||
btnText.style.display = 'inline';
|
|
||||||
btnLoading.style.display = 'none';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 显示提示消息
|
|
||||||
showToast(message, type = 'success') {
|
|
||||||
const toast = document.getElementById('toast');
|
|
||||||
toast.textContent = message;
|
|
||||||
toast.className = `toast ${type}`;
|
|
||||||
toast.classList.add('show');
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
toast.classList.remove('show');
|
|
||||||
}, 3000);
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTML转义
|
|
||||||
escapeHtml(text) {
|
|
||||||
const div = document.createElement('div');
|
|
||||||
div.textContent = text;
|
|
||||||
return div.innerHTML;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 页面加载完成后初始化
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
|
||||||
const generator = new KFCGenerator();
|
|
||||||
// 页面加载完成后自动生成一条文案
|
|
||||||
setTimeout(() => {
|
|
||||||
generator.generateKFC();
|
|
||||||
}, 1000);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 添加键盘快捷键支持
|
|
||||||
document.addEventListener('keydown', (event) => {
|
|
||||||
// 按空格键生成文案
|
|
||||||
if (event.code === 'Space' && event.target.tagName !== 'INPUT' && event.target.tagName !== 'TEXTAREA') {
|
|
||||||
event.preventDefault();
|
|
||||||
document.getElementById('generateBtn').click();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ctrl+C 复制文案
|
|
||||||
if (event.ctrlKey && event.key === 'c' && event.target.tagName !== 'INPUT' && event.target.tagName !== 'TEXTAREA') {
|
|
||||||
const copyBtn = document.getElementById('copyBtn');
|
|
||||||
if (copyBtn.style.display !== 'none') {
|
|
||||||
event.preventDefault();
|
|
||||||
copyBtn.click();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
[
|
|
||||||
"https://60s-cf.viki.moe",
|
|
||||||
"https://60s.viki.moe",
|
|
||||||
"https://60s.b23.run",
|
|
||||||
"https://60s.114128.xyz",
|
|
||||||
"https://60s-cf.114128.xyz"
|
|
||||||
]
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"code":200,"message":"获取成功。数据来自官方/权威源头,以确保稳定与实时。开源地址 https://github.com/vikiboss/60s,反馈群 595941841","data":{"index":369,"kfc":"我不想你带着情绪过夜,这样会把你推的越来越远,我不想你离开我,我希望你可以多爱我一点,我不想因为一点点小问题就让我们两之间的感情产生隔阂。对我来说,双向奔赴的感情相处才有意义,看不到希望的事情我没办法坚持太久,我不想浪费时间在一些无意义无结果的事情上,时间应该用来做我该做的事情。很多人走不到最后,是因为理解不同步,但我跟你在一起,我知道你什样的人,我了解你甚至比了解自己还多,我和你在一起,不是因为其他,而是因为今天肯德基疯狂星期四,我希望你能请我吃!"}}
|
|
||||||
@@ -1,167 +0,0 @@
|
|||||||
/* 背景样式文件 - 金色光辉主题 */
|
|
||||||
|
|
||||||
/* 主背景 */
|
|
||||||
body {
|
|
||||||
background: linear-gradient(
|
|
||||||
135deg,
|
|
||||||
#fff8dc 0%,
|
|
||||||
#ffeaa7 25%,
|
|
||||||
#fdcb6e 50%,
|
|
||||||
#e17055 75%,
|
|
||||||
#d63031 100%
|
|
||||||
);
|
|
||||||
background-size: 400% 400%;
|
|
||||||
animation: gradientShift 15s ease infinite;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 背景装饰层 */
|
|
||||||
body::before {
|
|
||||||
content: '';
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background:
|
|
||||||
radial-gradient(circle at 20% 80%, rgba(255, 215, 0, 0.1) 0%, transparent 50%),
|
|
||||||
radial-gradient(circle at 80% 20%, rgba(255, 223, 0, 0.1) 0%, transparent 50%),
|
|
||||||
radial-gradient(circle at 40% 40%, rgba(212, 175, 55, 0.05) 0%, transparent 50%);
|
|
||||||
pointer-events: none;
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 动态光点效果 */
|
|
||||||
body::after {
|
|
||||||
content: '';
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background-image:
|
|
||||||
radial-gradient(2px 2px at 20px 30px, rgba(255, 215, 0, 0.3), transparent),
|
|
||||||
radial-gradient(2px 2px at 40px 70px, rgba(255, 223, 0, 0.2), transparent),
|
|
||||||
radial-gradient(1px 1px at 90px 40px, rgba(212, 175, 55, 0.4), transparent),
|
|
||||||
radial-gradient(1px 1px at 130px 80px, rgba(255, 215, 0, 0.2), transparent),
|
|
||||||
radial-gradient(2px 2px at 160px 30px, rgba(255, 223, 0, 0.3), transparent);
|
|
||||||
background-repeat: repeat;
|
|
||||||
background-size: 200px 100px;
|
|
||||||
animation: sparkle 20s linear infinite;
|
|
||||||
pointer-events: none;
|
|
||||||
z-index: 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 背景动画 */
|
|
||||||
@keyframes gradientShift {
|
|
||||||
0% {
|
|
||||||
background-position: 0% 50%;
|
|
||||||
}
|
|
||||||
50% {
|
|
||||||
background-position: 100% 50%;
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
background-position: 0% 50%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes sparkle {
|
|
||||||
0% {
|
|
||||||
transform: translateY(0);
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
transform: translateY(-100vh);
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 响应式背景调整 */
|
|
||||||
|
|
||||||
/* 平板端背景 */
|
|
||||||
@media (min-width: 768px) and (max-width: 1024px) {
|
|
||||||
body::after {
|
|
||||||
background-size: 250px 120px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 电脑端背景 */
|
|
||||||
@media (min-width: 1024px) {
|
|
||||||
body {
|
|
||||||
background-size: 300% 300%;
|
|
||||||
}
|
|
||||||
|
|
||||||
body::after {
|
|
||||||
background-size: 300px 150px;
|
|
||||||
animation-duration: 25s;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 手机端背景优化 */
|
|
||||||
@media (max-width: 767px) {
|
|
||||||
body {
|
|
||||||
background-size: 200% 200%;
|
|
||||||
animation-duration: 10s;
|
|
||||||
}
|
|
||||||
|
|
||||||
body::before {
|
|
||||||
background:
|
|
||||||
radial-gradient(circle at 30% 70%, rgba(255, 215, 0, 0.08) 0%, transparent 40%),
|
|
||||||
radial-gradient(circle at 70% 30%, rgba(255, 223, 0, 0.08) 0%, transparent 40%);
|
|
||||||
}
|
|
||||||
|
|
||||||
body::after {
|
|
||||||
background-size: 150px 80px;
|
|
||||||
animation-duration: 15s;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 超小屏幕背景 */
|
|
||||||
@media (max-width: 479px) {
|
|
||||||
body {
|
|
||||||
background: linear-gradient(
|
|
||||||
135deg,
|
|
||||||
#fff8dc 0%,
|
|
||||||
#ffeaa7 50%,
|
|
||||||
#fdcb6e 100%
|
|
||||||
);
|
|
||||||
background-size: 150% 150%;
|
|
||||||
}
|
|
||||||
|
|
||||||
body::after {
|
|
||||||
background-size: 120px 60px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 深色模式支持 */
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
body {
|
|
||||||
background: linear-gradient(
|
|
||||||
135deg,
|
|
||||||
#2c1810 0%,
|
|
||||||
#3d2914 25%,
|
|
||||||
#4a3319 50%,
|
|
||||||
#5c3e1f 75%,
|
|
||||||
#6b4423 100%
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
body::before {
|
|
||||||
background:
|
|
||||||
radial-gradient(circle at 20% 80%, rgba(255, 215, 0, 0.05) 0%, transparent 50%),
|
|
||||||
radial-gradient(circle at 80% 20%, rgba(255, 223, 0, 0.05) 0%, transparent 50%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 减少动画效果(用户偏好) */
|
|
||||||
@media (prefers-reduced-motion: reduce) {
|
|
||||||
body,
|
|
||||||
body::before,
|
|
||||||
body::after {
|
|
||||||
animation: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
background: linear-gradient(135deg, #fff8dc 0%, #ffeaa7 50%, #fdcb6e 100%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,357 +0,0 @@
|
|||||||
/* 基础样式重置 */
|
|
||||||
* {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
font-family: 'Microsoft YaHei', 'PingFang SC', 'Helvetica Neue', Arial, sans-serif;
|
|
||||||
line-height: 1.6;
|
|
||||||
color: #2c1810;
|
|
||||||
overflow-x: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 容器布局 */
|
|
||||||
.container {
|
|
||||||
min-height: 100vh;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
padding: 20px;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 头部样式 */
|
|
||||||
.header {
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 40px;
|
|
||||||
z-index: 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
font-size: 3rem;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #d4af37;
|
|
||||||
text-shadow:
|
|
||||||
0 0 10px rgba(212, 175, 55, 0.8),
|
|
||||||
0 0 20px rgba(212, 175, 55, 0.6),
|
|
||||||
0 0 30px rgba(212, 175, 55, 0.4);
|
|
||||||
margin-bottom: 10px;
|
|
||||||
animation: titleGlow 3s ease-in-out infinite alternate;
|
|
||||||
}
|
|
||||||
|
|
||||||
.subtitle {
|
|
||||||
font-size: 1.2rem;
|
|
||||||
color: #b8860b;
|
|
||||||
opacity: 0.9;
|
|
||||||
text-shadow: 0 0 5px rgba(184, 134, 11, 0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 主内容区域 */
|
|
||||||
.main-content {
|
|
||||||
width: 100%;
|
|
||||||
max-width: 800px;
|
|
||||||
z-index: 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 一言容器 */
|
|
||||||
.quote-container {
|
|
||||||
background: linear-gradient(135deg, rgba(255, 215, 0, 0.1), rgba(255, 223, 0, 0.05));
|
|
||||||
border: 2px solid rgba(212, 175, 55, 0.3);
|
|
||||||
border-radius: 20px;
|
|
||||||
padding: 40px;
|
|
||||||
margin-bottom: 30px;
|
|
||||||
backdrop-filter: blur(10px);
|
|
||||||
box-shadow:
|
|
||||||
0 8px 32px rgba(212, 175, 55, 0.2),
|
|
||||||
inset 0 1px 0 rgba(255, 255, 255, 0.1);
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.quote-container::before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
top: -2px;
|
|
||||||
left: -2px;
|
|
||||||
right: -2px;
|
|
||||||
bottom: -2px;
|
|
||||||
background: linear-gradient(45deg, #ffd700, #ffed4e, #ffd700, #ffed4e);
|
|
||||||
border-radius: 22px;
|
|
||||||
z-index: -1;
|
|
||||||
animation: borderGlow 4s linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 加载状态 */
|
|
||||||
.loading {
|
|
||||||
display: none;
|
|
||||||
text-align: center;
|
|
||||||
color: #d4af37;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading.show {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading-spinner {
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
border: 4px solid rgba(212, 175, 55, 0.3);
|
|
||||||
border-top: 4px solid #d4af37;
|
|
||||||
border-radius: 50%;
|
|
||||||
margin: 0 auto 15px;
|
|
||||||
animation: spin 1s linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 一言显示 */
|
|
||||||
.quote-display {
|
|
||||||
display: block;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.quote-display.hide {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.quote-text {
|
|
||||||
font-size: 1.8rem;
|
|
||||||
line-height: 1.8;
|
|
||||||
color: #2c1810;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
text-shadow: 0 1px 2px rgba(212, 175, 55, 0.1);
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.quote-index {
|
|
||||||
font-size: 0.9rem;
|
|
||||||
color: #b8860b;
|
|
||||||
opacity: 0.8;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 错误信息 */
|
|
||||||
.error-message {
|
|
||||||
display: none;
|
|
||||||
text-align: center;
|
|
||||||
color: #cd853f;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-message.show {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-icon {
|
|
||||||
font-size: 2rem;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-text {
|
|
||||||
font-size: 1.1rem;
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 控制按钮 */
|
|
||||||
.controls {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.refresh-btn {
|
|
||||||
background: linear-gradient(135deg, #ffd700, #ffed4e);
|
|
||||||
border: none;
|
|
||||||
border-radius: 50px;
|
|
||||||
padding: 15px 30px;
|
|
||||||
font-size: 1.1rem;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #2c1810;
|
|
||||||
cursor: pointer;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
box-shadow:
|
|
||||||
0 4px 15px rgba(212, 175, 55, 0.3),
|
|
||||||
inset 0 1px 0 rgba(255, 255, 255, 0.3);
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.refresh-btn:hover {
|
|
||||||
transform: translateY(-2px);
|
|
||||||
box-shadow:
|
|
||||||
0 6px 20px rgba(212, 175, 55, 0.4),
|
|
||||||
inset 0 1px 0 rgba(255, 255, 255, 0.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
.refresh-btn:active {
|
|
||||||
transform: translateY(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
.refresh-btn:disabled {
|
|
||||||
opacity: 0.6;
|
|
||||||
cursor: not-allowed;
|
|
||||||
transform: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-icon {
|
|
||||||
font-size: 1.2rem;
|
|
||||||
transition: transform 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.refresh-btn:hover .btn-icon {
|
|
||||||
transform: rotate(180deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 底部 */
|
|
||||||
.footer {
|
|
||||||
margin-top: 40px;
|
|
||||||
text-align: center;
|
|
||||||
color: #b8860b;
|
|
||||||
opacity: 0.8;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 动画效果 */
|
|
||||||
@keyframes titleGlow {
|
|
||||||
0% {
|
|
||||||
text-shadow:
|
|
||||||
0 0 10px rgba(212, 175, 55, 0.8),
|
|
||||||
0 0 20px rgba(212, 175, 55, 0.6),
|
|
||||||
0 0 30px rgba(212, 175, 55, 0.4);
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
text-shadow:
|
|
||||||
0 0 15px rgba(212, 175, 55, 1),
|
|
||||||
0 0 25px rgba(212, 175, 55, 0.8),
|
|
||||||
0 0 35px rgba(212, 175, 55, 0.6);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes borderGlow {
|
|
||||||
0% {
|
|
||||||
background-position: 0% 50%;
|
|
||||||
}
|
|
||||||
50% {
|
|
||||||
background-position: 100% 50%;
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
background-position: 0% 50%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes spin {
|
|
||||||
0% { transform: rotate(0deg); }
|
|
||||||
100% { transform: rotate(360deg); }
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 平板端适配 (768px - 1024px) */
|
|
||||||
@media (min-width: 768px) and (max-width: 1024px) {
|
|
||||||
.container {
|
|
||||||
padding: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
font-size: 3.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.subtitle {
|
|
||||||
font-size: 1.3rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.quote-container {
|
|
||||||
padding: 50px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.quote-text {
|
|
||||||
font-size: 2rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 电脑端适配 (1024px+) */
|
|
||||||
@media (min-width: 1024px) {
|
|
||||||
.container {
|
|
||||||
padding: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
font-size: 4rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.subtitle {
|
|
||||||
font-size: 1.4rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.quote-container {
|
|
||||||
padding: 60px;
|
|
||||||
max-width: 900px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.quote-text {
|
|
||||||
font-size: 2.2rem;
|
|
||||||
line-height: 1.9;
|
|
||||||
}
|
|
||||||
|
|
||||||
.refresh-btn {
|
|
||||||
padding: 18px 36px;
|
|
||||||
font-size: 1.2rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 手机端适配 (小于768px) */
|
|
||||||
@media (max-width: 767px) {
|
|
||||||
.container {
|
|
||||||
padding: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header {
|
|
||||||
margin-bottom: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
font-size: 2.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.subtitle {
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.quote-container {
|
|
||||||
padding: 25px;
|
|
||||||
border-radius: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.quote-text {
|
|
||||||
font-size: 1.4rem;
|
|
||||||
line-height: 1.6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.refresh-btn {
|
|
||||||
padding: 12px 24px;
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer {
|
|
||||||
margin-top: 30px;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 超小屏幕适配 (小于480px) */
|
|
||||||
@media (max-width: 479px) {
|
|
||||||
.title {
|
|
||||||
font-size: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.quote-container {
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.quote-text {
|
|
||||||
font-size: 1.2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.refresh-btn {
|
|
||||||
padding: 10px 20px;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="zh-CN">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>随机一言 - 金色光辉</title>
|
|
||||||
<link rel="stylesheet" href="css/style.css">
|
|
||||||
<link rel="stylesheet" href="css/background.css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="container">
|
|
||||||
<header class="header">
|
|
||||||
<h1 class="title">随机一言</h1>
|
|
||||||
<p class="subtitle">每一句话都是心灵的光芒</p>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main class="main-content">
|
|
||||||
<div class="quote-container">
|
|
||||||
<div class="loading" id="loading">
|
|
||||||
<div class="loading-spinner"></div>
|
|
||||||
<p>正在获取一言...</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="quote-display" id="quoteDisplay">
|
|
||||||
<div class="quote-text" id="quoteText">
|
|
||||||
点击下方按钮获取一言
|
|
||||||
</div>
|
|
||||||
<div class="quote-index" id="quoteIndex"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="error-message" id="errorMessage">
|
|
||||||
<div class="error-icon">⚠️</div>
|
|
||||||
<div class="error-text" id="errorText"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="controls">
|
|
||||||
<button class="refresh-btn" id="refreshBtn">
|
|
||||||
<span class="btn-icon">🔄</span>
|
|
||||||
<span class="btn-text">获取新一言</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<footer class="footer">
|
|
||||||
<p>愿每一句话都能温暖你的心</p>
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src="js/script.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,225 +0,0 @@
|
|||||||
// 随机一言 JavaScript 功能实现
|
|
||||||
|
|
||||||
class HitokotoApp {
|
|
||||||
constructor() {
|
|
||||||
// API接口列表
|
|
||||||
this.apiEndpoints = [
|
|
||||||
"https://60s-cf.viki.moe",
|
|
||||||
"https://60s.viki.moe",
|
|
||||||
"https://60s.b23.run",
|
|
||||||
"https://60s.114128.xyz",
|
|
||||||
"https://60s-cf.114128.xyz"
|
|
||||||
];
|
|
||||||
|
|
||||||
this.currentEndpointIndex = 0;
|
|
||||||
this.isLoading = false;
|
|
||||||
|
|
||||||
// DOM 元素
|
|
||||||
this.elements = {
|
|
||||||
loading: document.getElementById('loading'),
|
|
||||||
quoteDisplay: document.getElementById('quoteDisplay'),
|
|
||||||
quoteText: document.getElementById('quoteText'),
|
|
||||||
quoteIndex: document.getElementById('quoteIndex'),
|
|
||||||
errorMessage: document.getElementById('errorMessage'),
|
|
||||||
errorText: document.getElementById('errorText'),
|
|
||||||
refreshBtn: document.getElementById('refreshBtn')
|
|
||||||
};
|
|
||||||
|
|
||||||
this.init();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 初始化应用
|
|
||||||
init() {
|
|
||||||
this.bindEvents();
|
|
||||||
this.hideAllStates();
|
|
||||||
this.showQuoteDisplay();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 绑定事件
|
|
||||||
bindEvents() {
|
|
||||||
this.elements.refreshBtn.addEventListener('click', () => {
|
|
||||||
this.fetchHitokoto();
|
|
||||||
});
|
|
||||||
|
|
||||||
// 键盘快捷键支持
|
|
||||||
document.addEventListener('keydown', (e) => {
|
|
||||||
if (e.code === 'Space' && !this.isLoading) {
|
|
||||||
e.preventDefault();
|
|
||||||
this.fetchHitokoto();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 隐藏所有状态
|
|
||||||
hideAllStates() {
|
|
||||||
this.elements.loading.classList.remove('show');
|
|
||||||
this.elements.quoteDisplay.classList.remove('hide');
|
|
||||||
this.elements.errorMessage.classList.remove('show');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 显示加载状态
|
|
||||||
showLoading() {
|
|
||||||
this.hideAllStates();
|
|
||||||
this.elements.loading.classList.add('show');
|
|
||||||
this.elements.quoteDisplay.classList.add('hide');
|
|
||||||
this.elements.refreshBtn.disabled = true;
|
|
||||||
this.isLoading = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 显示一言内容
|
|
||||||
showQuoteDisplay() {
|
|
||||||
this.hideAllStates();
|
|
||||||
this.elements.quoteDisplay.classList.remove('hide');
|
|
||||||
this.elements.refreshBtn.disabled = false;
|
|
||||||
this.isLoading = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 显示错误信息
|
|
||||||
showError(message) {
|
|
||||||
this.hideAllStates();
|
|
||||||
this.elements.errorMessage.classList.add('show');
|
|
||||||
this.elements.errorText.textContent = message;
|
|
||||||
this.elements.refreshBtn.disabled = false;
|
|
||||||
this.isLoading = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取一言数据
|
|
||||||
async fetchHitokoto() {
|
|
||||||
if (this.isLoading) return;
|
|
||||||
|
|
||||||
this.showLoading();
|
|
||||||
|
|
||||||
// 尝试所有API接口
|
|
||||||
for (let i = 0; i < this.apiEndpoints.length; i++) {
|
|
||||||
const endpointIndex = (this.currentEndpointIndex + i) % this.apiEndpoints.length;
|
|
||||||
const endpoint = this.apiEndpoints[endpointIndex];
|
|
||||||
|
|
||||||
try {
|
|
||||||
const result = await this.tryFetchFromEndpoint(endpoint);
|
|
||||||
if (result.success) {
|
|
||||||
this.currentEndpointIndex = endpointIndex;
|
|
||||||
this.displayHitokoto(result.data);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.warn(`接口 ${endpoint} 请求失败:`, error.message);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 所有接口都失败
|
|
||||||
this.showError('所有接口都无法访问,请检查网络连接或稍后重试');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 尝试从指定接口获取数据
|
|
||||||
async tryFetchFromEndpoint(endpoint) {
|
|
||||||
const controller = new AbortController();
|
|
||||||
const timeoutId = setTimeout(() => controller.abort(), 10000); // 10秒超时
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch(`${endpoint}/v2/hitokoto?encoding=text`, {
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
'Accept': 'application/json',
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
signal: controller.signal
|
|
||||||
});
|
|
||||||
|
|
||||||
clearTimeout(timeoutId);
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
// 验证返回数据格式
|
|
||||||
if (data.code === 200 && data.data && data.data.hitokoto) {
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
data: data.data
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
throw new Error('返回数据格式不正确');
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
clearTimeout(timeoutId);
|
|
||||||
|
|
||||||
if (error.name === 'AbortError') {
|
|
||||||
throw new Error('请求超时');
|
|
||||||
}
|
|
||||||
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 显示一言内容
|
|
||||||
displayHitokoto(data) {
|
|
||||||
// 更新一言文本
|
|
||||||
this.elements.quoteText.textContent = data.hitokoto;
|
|
||||||
|
|
||||||
// 更新序号信息
|
|
||||||
if (data.index) {
|
|
||||||
this.elements.quoteIndex.textContent = `第 ${data.index} 条`;
|
|
||||||
} else {
|
|
||||||
this.elements.quoteIndex.textContent = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加淡入动画效果
|
|
||||||
this.elements.quoteText.style.opacity = '0';
|
|
||||||
this.elements.quoteIndex.style.opacity = '0';
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
this.elements.quoteText.style.transition = 'opacity 0.5s ease';
|
|
||||||
this.elements.quoteIndex.style.transition = 'opacity 0.5s ease';
|
|
||||||
this.elements.quoteText.style.opacity = '1';
|
|
||||||
this.elements.quoteIndex.style.opacity = '1';
|
|
||||||
}, 100);
|
|
||||||
|
|
||||||
this.showQuoteDisplay();
|
|
||||||
|
|
||||||
// 控制台输出调试信息
|
|
||||||
console.log('一言获取成功:', {
|
|
||||||
content: data.hitokoto,
|
|
||||||
index: data.index,
|
|
||||||
endpoint: this.apiEndpoints[this.currentEndpointIndex]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取随机接口(用于负载均衡)
|
|
||||||
getRandomEndpoint() {
|
|
||||||
const randomIndex = Math.floor(Math.random() * this.apiEndpoints.length);
|
|
||||||
return this.apiEndpoints[randomIndex];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 页面加载完成后初始化应用
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
|
||||||
const app = new HitokotoApp();
|
|
||||||
|
|
||||||
// 添加全局错误处理
|
|
||||||
window.addEventListener('error', (event) => {
|
|
||||||
console.error('页面发生错误:', event.error);
|
|
||||||
});
|
|
||||||
|
|
||||||
window.addEventListener('unhandledrejection', (event) => {
|
|
||||||
console.error('未处理的Promise拒绝:', event.reason);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 页面可见性变化时的处理
|
|
||||||
document.addEventListener('visibilitychange', () => {
|
|
||||||
if (!document.hidden && !app.isLoading) {
|
|
||||||
// 页面重新可见时,可以选择刷新内容
|
|
||||||
console.log('页面重新可见');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('随机一言应用初始化完成');
|
|
||||||
});
|
|
||||||
|
|
||||||
// 导出应用类(如果需要在其他地方使用)
|
|
||||||
if (typeof module !== 'undefined' && module.exports) {
|
|
||||||
module.exports = HitokotoApp;
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
[
|
|
||||||
"https://60s-cf.viki.moe",
|
|
||||||
"https://60s.viki.moe",
|
|
||||||
"https://60s.b23.run",
|
|
||||||
"https://60s.114128.xyz",
|
|
||||||
"https://60s-cf.114128.xyz"
|
|
||||||
]
|
|
||||||
@@ -1,251 +0,0 @@
|
|||||||
/* 随机唱歌音频 - 淡绿色清新风格样式 */
|
|
||||||
|
|
||||||
/* 重置样式 */
|
|
||||||
* {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
||||||
background: linear-gradient(135deg, #a8e6cf 0%, #dcedc1 50%, #ffd3a5 100%);
|
|
||||||
min-height: 100vh;
|
|
||||||
color: #2d5016;
|
|
||||||
line-height: 1.6;
|
|
||||||
overflow-x: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
max-width: 900px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 头部 */
|
|
||||||
.header {
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
background: rgba(255, 255, 255, 0.85);
|
|
||||||
border-radius: 20px;
|
|
||||||
padding: 24px;
|
|
||||||
box-shadow: 0 8px 25px rgba(45, 80, 22, 0.08);
|
|
||||||
backdrop-filter: blur(10px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.header h1 {
|
|
||||||
font-size: 2rem;
|
|
||||||
color: #2d5016;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
font-weight: 700;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header p {
|
|
||||||
color: #5a7c65;
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 用户卡片 */
|
|
||||||
.user-card {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 15px;
|
|
||||||
background: rgba(255, 255, 255, 0.9);
|
|
||||||
padding: 16px;
|
|
||||||
border-radius: 15px;
|
|
||||||
box-shadow: 0 4px 18px rgba(45, 80, 22, 0.08);
|
|
||||||
margin-bottom: 15px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.avatar {
|
|
||||||
width: 56px;
|
|
||||||
height: 56px;
|
|
||||||
border-radius: 50%;
|
|
||||||
object-fit: cover;
|
|
||||||
border: 3px solid rgba(129, 199, 132, 0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
.user-info {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nickname {
|
|
||||||
font-weight: 700;
|
|
||||||
font-size: 1.1rem;
|
|
||||||
color: #2d5016;
|
|
||||||
}
|
|
||||||
|
|
||||||
.meta {
|
|
||||||
color: #5a7c65;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 歌曲信息 */
|
|
||||||
.song-card {
|
|
||||||
background: rgba(255, 255, 255, 0.9);
|
|
||||||
padding: 16px;
|
|
||||||
border-radius: 15px;
|
|
||||||
box-shadow: 0 4px 18px rgba(45, 80, 22, 0.08);
|
|
||||||
margin-bottom: 15px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.song-title {
|
|
||||||
font-size: 1.2rem;
|
|
||||||
font-weight: 700;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
color: #1b5e20;
|
|
||||||
}
|
|
||||||
|
|
||||||
.song-meta {
|
|
||||||
color: #5a7c65;
|
|
||||||
font-size: 0.95rem;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 歌词 */
|
|
||||||
.lyrics {
|
|
||||||
background: rgba(129, 199, 132, 0.1);
|
|
||||||
border-radius: 12px;
|
|
||||||
padding: 12px;
|
|
||||||
max-height: 220px;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lyrics p {
|
|
||||||
margin-bottom: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 音频播放器卡片 */
|
|
||||||
.audio-card {
|
|
||||||
background: rgba(255, 255, 255, 0.9);
|
|
||||||
padding: 16px;
|
|
||||||
border-radius: 15px;
|
|
||||||
box-shadow: 0 4px 18px rgba(45, 80, 22, 0.08);
|
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.audio-actions {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 12px;
|
|
||||||
align-items: center;
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn {
|
|
||||||
background: linear-gradient(135deg, #81c784 0%, #66bb6a 100%);
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
padding: 10px 18px;
|
|
||||||
border-radius: 10px;
|
|
||||||
font-size: 0.95rem;
|
|
||||||
font-weight: 600;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.25s ease;
|
|
||||||
box-shadow: 0 4px 12px rgba(129, 199, 132, 0.35);
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn:hover {
|
|
||||||
transform: translateY(-2px);
|
|
||||||
box-shadow: 0 6px 18px rgba(129, 199, 132, 0.45);
|
|
||||||
}
|
|
||||||
|
|
||||||
.info {
|
|
||||||
color: #5a7c65;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 加载与错误 */
|
|
||||||
.loading, .error {
|
|
||||||
text-align: center;
|
|
||||||
padding: 30px;
|
|
||||||
background: rgba(255, 255, 255, 0.85);
|
|
||||||
border-radius: 15px;
|
|
||||||
box-shadow: 0 5px 20px rgba(45, 80, 22, 0.08);
|
|
||||||
}
|
|
||||||
|
|
||||||
.spinner {
|
|
||||||
width: 36px;
|
|
||||||
height: 36px;
|
|
||||||
border: 4px solid #e8f5e8;
|
|
||||||
border-top: 4px solid #81c784;
|
|
||||||
border-radius: 50%;
|
|
||||||
animation: spin 1s linear infinite;
|
|
||||||
margin: 0 auto 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes spin {
|
|
||||||
0% { transform: rotate(0deg); }
|
|
||||||
100% { transform: rotate(360deg); }
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 动画 */
|
|
||||||
.fade-in {
|
|
||||||
animation: fadeIn 0.5s ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes fadeIn {
|
|
||||||
from { opacity: 0; transform: translateY(10px); }
|
|
||||||
to { opacity: 1; transform: translateY(0); }
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 平板端适配 */
|
|
||||||
@media (max-width: 1024px) and (min-width: 768px) {
|
|
||||||
.container { padding: 16px; }
|
|
||||||
.header h1 { font-size: 1.8rem; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 手机端优先优化 */
|
|
||||||
@media (max-width: 767px) {
|
|
||||||
.container { padding: 12px; }
|
|
||||||
.header { padding: 18px; }
|
|
||||||
.header h1 { font-size: 1.6rem; gap: 8px; }
|
|
||||||
|
|
||||||
.user-card {
|
|
||||||
padding: 16px;
|
|
||||||
flex-direction: column;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.avatar {
|
|
||||||
width: 80px;
|
|
||||||
height: 80px;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.song-card, .audio-card {
|
|
||||||
padding: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lyrics {
|
|
||||||
max-height: 180px;
|
|
||||||
text-align: left;
|
|
||||||
padding: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.audio-actions {
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 15px;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info {
|
|
||||||
text-align: center;
|
|
||||||
line-height: 1.8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn {
|
|
||||||
width: 100%;
|
|
||||||
max-width: 200px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="zh-CN">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
|
||||||
<title>随机唱歌音频 - 60s API 集合</title>
|
|
||||||
<meta name="description" content="随机唱歌音频,数据源自 60s.viki.moe,提供用户信息、歌曲信息、歌词与音频播放。" />
|
|
||||||
|
|
||||||
<link rel="stylesheet" href="./css/style.css" />
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="container">
|
|
||||||
<header class="header">
|
|
||||||
<h1>
|
|
||||||
🎵 随机唱歌音频
|
|
||||||
</h1>
|
|
||||||
<p>数据来自官方/权威源头,以确保稳定与实时 · 支持本地数据回退</p>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<!-- 加载与错误状态 -->
|
|
||||||
<section id="loading" class="loading">
|
|
||||||
<div class="spinner"></div>
|
|
||||||
<p>正在加载中,请稍候…</p>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section id="error" class="error" style="display: none;">
|
|
||||||
<p>获取数据失败,请稍后重试</p>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<!-- 内容区域 -->
|
|
||||||
<main id="content" style="display: none;" class="fade-in">
|
|
||||||
<!-- 用户信息 -->
|
|
||||||
<div class="user-card">
|
|
||||||
<img id="avatar" class="avatar" src="" alt="用户头像" />
|
|
||||||
<div class="user-info">
|
|
||||||
<div class="nickname" id="nickname">-</div>
|
|
||||||
<div class="meta">性别:<span id="gender">-</span></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 歌曲信息 -->
|
|
||||||
<div class="song-card">
|
|
||||||
<div class="song-title" id="song-title">-</div>
|
|
||||||
<div class="song-meta" id="song-meta">-</div>
|
|
||||||
<div class="lyrics" id="lyrics"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 音频播放 -->
|
|
||||||
<div class="audio-card">
|
|
||||||
<audio id="audio" controls style="width: 100%;"></audio>
|
|
||||||
<div class="audio-actions">
|
|
||||||
<button class="btn" id="refresh-btn">换一首</button>
|
|
||||||
<div class="info">
|
|
||||||
❤ 喜欢:<span id="like-count">-</span>
|
|
||||||
· ⏱ 时长:<span id="duration">--:--</span>
|
|
||||||
· 🗓 发布:<span id="publish-time">-</span>
|
|
||||||
· 🔗 <a id="link" href="#" class="btn" style="padding: 6px 10px; border-radius: 8px; background: #81c784;">查看原帖</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src="./js/script.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,252 +0,0 @@
|
|||||||
// 随机唱歌音频 页面脚本
|
|
||||||
(function () {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const API = {
|
|
||||||
endpoints: [],
|
|
||||||
currentIndex: 0,
|
|
||||||
params: {
|
|
||||||
encoding: 'json'
|
|
||||||
},
|
|
||||||
localFallback: '返回接口.json',
|
|
||||||
// 初始化API接口列表
|
|
||||||
async init() {
|
|
||||||
try {
|
|
||||||
const res = await fetch('./接口集合.json');
|
|
||||||
const endpoints = await res.json();
|
|
||||||
this.endpoints = endpoints.map(endpoint => `${endpoint}/v2/changya`);
|
|
||||||
} catch (e) {
|
|
||||||
// 如果无法加载接口集合,使用默认接口
|
|
||||||
this.endpoints = ['https://60s.viki.moe/v2/changya'];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// 获取当前接口URL
|
|
||||||
getCurrentUrl() {
|
|
||||||
if (this.endpoints.length === 0) return null;
|
|
||||||
const url = new URL(this.endpoints[this.currentIndex]);
|
|
||||||
Object.entries(this.params).forEach(([k, v]) => url.searchParams.append(k, v));
|
|
||||||
return url.toString();
|
|
||||||
},
|
|
||||||
// 切换到下一个接口
|
|
||||||
switchToNext() {
|
|
||||||
this.currentIndex = (this.currentIndex + 1) % this.endpoints.length;
|
|
||||||
return this.currentIndex < this.endpoints.length;
|
|
||||||
},
|
|
||||||
// 重置到第一个接口
|
|
||||||
reset() {
|
|
||||||
this.currentIndex = 0;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// DOM 元素引用
|
|
||||||
const els = {
|
|
||||||
loading: null,
|
|
||||||
error: null,
|
|
||||||
container: null,
|
|
||||||
avatar: null,
|
|
||||||
nickname: null,
|
|
||||||
gender: null,
|
|
||||||
songTitle: null,
|
|
||||||
songMeta: null,
|
|
||||||
lyrics: null,
|
|
||||||
audio: null,
|
|
||||||
likeCount: null,
|
|
||||||
publishTime: null,
|
|
||||||
link: null,
|
|
||||||
refreshBtn: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
function initDom() {
|
|
||||||
els.loading = document.getElementById('loading');
|
|
||||||
els.error = document.getElementById('error');
|
|
||||||
els.container = document.getElementById('content');
|
|
||||||
|
|
||||||
els.avatar = document.getElementById('avatar');
|
|
||||||
els.nickname = document.getElementById('nickname');
|
|
||||||
els.gender = document.getElementById('gender');
|
|
||||||
els.songTitle = document.getElementById('song-title');
|
|
||||||
els.songMeta = document.getElementById('song-meta');
|
|
||||||
els.lyrics = document.getElementById('lyrics');
|
|
||||||
|
|
||||||
els.audio = document.getElementById('audio');
|
|
||||||
els.likeCount = document.getElementById('like-count');
|
|
||||||
els.publishTime = document.getElementById('publish-time');
|
|
||||||
els.link = document.getElementById('link');
|
|
||||||
els.refreshBtn = document.getElementById('refresh-btn');
|
|
||||||
}
|
|
||||||
|
|
||||||
function showLoading() {
|
|
||||||
els.loading.style.display = 'block';
|
|
||||||
els.error.style.display = 'none';
|
|
||||||
els.container.style.display = 'none';
|
|
||||||
}
|
|
||||||
|
|
||||||
function showError(msg) {
|
|
||||||
els.loading.style.display = 'none';
|
|
||||||
els.error.style.display = 'block';
|
|
||||||
els.container.style.display = 'none';
|
|
||||||
els.error.querySelector('p').textContent = msg || '获取数据失败,请稍后重试';
|
|
||||||
}
|
|
||||||
|
|
||||||
function showContent() {
|
|
||||||
els.loading.style.display = 'none';
|
|
||||||
els.error.style.display = 'none';
|
|
||||||
els.container.style.display = 'block';
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatDuration(ms) {
|
|
||||||
if (!ms && ms !== 0) return '';
|
|
||||||
const totalSeconds = Math.floor(ms / 1000);
|
|
||||||
const m = Math.floor(totalSeconds / 60).toString().padStart(2, '0');
|
|
||||||
const s = (totalSeconds % 60).toString().padStart(2, '0');
|
|
||||||
return `${m}:${s}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function safeText(text) {
|
|
||||||
const div = document.createElement('div');
|
|
||||||
div.textContent = text == null ? '' : String(text);
|
|
||||||
return div.innerHTML;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchFromAPI() {
|
|
||||||
// 初始化API接口列表
|
|
||||||
await API.init();
|
|
||||||
|
|
||||||
// 重置API索引到第一个接口
|
|
||||||
API.reset();
|
|
||||||
|
|
||||||
// 尝试所有API接口
|
|
||||||
for (let i = 0; i < API.endpoints.length; i++) {
|
|
||||||
try {
|
|
||||||
const url = API.getCurrentUrl();
|
|
||||||
console.log(`尝试接口 ${i + 1}/${API.endpoints.length}: ${url}`);
|
|
||||||
|
|
||||||
const resp = await fetch(url, {
|
|
||||||
cache: 'no-store',
|
|
||||||
timeout: 10000 // 10秒超时
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!resp.ok) {
|
|
||||||
throw new Error(`HTTP ${resp.status}: ${resp.statusText}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await resp.json();
|
|
||||||
|
|
||||||
if (data && data.code === 200) {
|
|
||||||
console.log(`接口 ${i + 1} 请求成功`);
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error(data && data.message ? data.message : '接口返回异常');
|
|
||||||
|
|
||||||
} catch (e) {
|
|
||||||
console.warn(`接口 ${i + 1} 失败:`, e.message);
|
|
||||||
|
|
||||||
// 如果不是最后一个接口,切换到下一个
|
|
||||||
if (i < API.endpoints.length - 1) {
|
|
||||||
API.switchToNext();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 所有接口都失败了
|
|
||||||
console.warn('所有远程接口都失败,尝试本地数据');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchFromLocal() {
|
|
||||||
try {
|
|
||||||
const resp = await fetch(API.localFallback + `?t=${Date.now()}`);
|
|
||||||
if (!resp.ok) throw new Error(`本地文件HTTP ${resp.status}`);
|
|
||||||
const data = await resp.json();
|
|
||||||
return data;
|
|
||||||
} catch (e) {
|
|
||||||
console.error('读取本地返回接口.json失败:', e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function render(data) {
|
|
||||||
const d = data?.data || {};
|
|
||||||
const user = d.user || {};
|
|
||||||
const song = d.song || {};
|
|
||||||
const audio = d.audio || {};
|
|
||||||
|
|
||||||
// 用户信息
|
|
||||||
els.avatar.src = user.avatar_url || '';
|
|
||||||
els.avatar.alt = (user.nickname || '用户') + ' 头像';
|
|
||||||
els.nickname.textContent = user.nickname || '未知用户';
|
|
||||||
els.gender.textContent = user.gender === 'female' ? '女' : user.gender === 'male' ? '男' : '未知';
|
|
||||||
|
|
||||||
// 歌曲信息
|
|
||||||
els.songTitle.textContent = song.name || '未知歌曲';
|
|
||||||
els.songMeta.textContent = song.singer ? `演唱:${song.singer}` : '';
|
|
||||||
|
|
||||||
els.lyrics.innerHTML = '';
|
|
||||||
if (Array.isArray(song.lyrics)) {
|
|
||||||
const frag = document.createDocumentFragment();
|
|
||||||
song.lyrics.forEach(line => {
|
|
||||||
const p = document.createElement('p');
|
|
||||||
p.innerHTML = safeText(line);
|
|
||||||
frag.appendChild(p);
|
|
||||||
});
|
|
||||||
els.lyrics.appendChild(frag);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 音频
|
|
||||||
els.audio.src = audio.url || '';
|
|
||||||
els.audio.preload = 'none';
|
|
||||||
|
|
||||||
// 其他信息
|
|
||||||
els.likeCount.textContent = typeof audio.like_count === 'number' ? audio.like_count : '-';
|
|
||||||
const publish = audio.publish || (audio.publish_at ? new Date(audio.publish_at).toLocaleString() : '');
|
|
||||||
els.publishTime.textContent = publish;
|
|
||||||
els.link.href = audio.link || '#';
|
|
||||||
els.link.target = '_blank';
|
|
||||||
|
|
||||||
// 时长信息
|
|
||||||
const durationEl = document.getElementById('duration');
|
|
||||||
durationEl.textContent = formatDuration(audio.duration);
|
|
||||||
|
|
||||||
showContent();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function load() {
|
|
||||||
showLoading();
|
|
||||||
try {
|
|
||||||
// 先尝试远程API
|
|
||||||
const data = await fetchFromAPI();
|
|
||||||
if (data) {
|
|
||||||
render(data);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 远程API失败,尝试本地数据
|
|
||||||
const localData = await fetchFromLocal();
|
|
||||||
if (localData) {
|
|
||||||
render(localData);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 都失败了
|
|
||||||
showError('获取数据失败,请稍后重试');
|
|
||||||
} catch (e) {
|
|
||||||
console.error('加载数据时发生错误:', e);
|
|
||||||
showError('获取数据失败,请稍后重试');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function bindEvents() {
|
|
||||||
if (els.refreshBtn) {
|
|
||||||
els.refreshBtn.addEventListener('click', load);
|
|
||||||
}
|
|
||||||
// 快捷键 Ctrl+R 刷新(不拦截浏览器默认刷新)
|
|
||||||
}
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
|
||||||
initDom();
|
|
||||||
bindEvents();
|
|
||||||
load();
|
|
||||||
});
|
|
||||||
})();
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
[
|
|
||||||
"https://60s-cf.viki.moe",
|
|
||||||
"https://60s.viki.moe",
|
|
||||||
"https://60s.b23.run",
|
|
||||||
"https://60s.114128.xyz",
|
|
||||||
"https://60s-cf.114128.xyz"
|
|
||||||
]
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
{
|
|
||||||
"code": 200,
|
|
||||||
"message": "获取成功。数据来自官方/权威源头,以确保稳定与实时。开源地址 https://github.com/vikiboss/60s,反馈群 595941841",
|
|
||||||
"data": {
|
|
||||||
"user": {
|
|
||||||
"nickname": "𝑮𝑺_迷鹿_",
|
|
||||||
"gender": "female",
|
|
||||||
"avatar_url": "http://img-cdn.api.singduck.cn/user-img/6afbebcfae6144478c150d0c1d0d5899.jpg"
|
|
||||||
},
|
|
||||||
"song": {
|
|
||||||
"name": "恶作剧",
|
|
||||||
"singer": "王蓝茵",
|
|
||||||
"lyrics": [
|
|
||||||
"我想我会开始想念你",
|
|
||||||
"可是我刚刚才遇见了你",
|
|
||||||
"我怀疑这奇遇只是个恶作剧",
|
|
||||||
"我想我已慢慢喜欢你",
|
|
||||||
"因为我拥有爱情的勇气",
|
|
||||||
"我任性投入你给的恶作剧",
|
|
||||||
"你给的恶作剧"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"audio": {
|
|
||||||
"url": "http://audio-cdn.api.singduck.cn/ugc/220929_965696173_b822a290c553.wav?auth_key=1755845643-0-0-4029539b73e17337dcac49cc4e0ecfcc",
|
|
||||||
"duration": 35050,
|
|
||||||
"like_count": 955,
|
|
||||||
"link": "https://m.api.singduck.cn/user-piece/toGZlBfZbukck2sHb",
|
|
||||||
"publish": "2022/09/29 18:33:51",
|
|
||||||
"publish_at": 1664447631000
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,330 +0,0 @@
|
|||||||
/* Epic Games 免费游戏 - 淡绿色清新风格样式 */
|
|
||||||
|
|
||||||
/* 重置样式 */
|
|
||||||
* {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
||||||
background: linear-gradient(135deg, #a8e6cf 0%, #dcedc1 50%, #ffd3a5 100%);
|
|
||||||
min-height: 100vh;
|
|
||||||
color: #2d5016;
|
|
||||||
line-height: 1.6;
|
|
||||||
overflow-x: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
max-width: 1200px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 头部 */
|
|
||||||
.header {
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 30px;
|
|
||||||
background: rgba(255, 255, 255, 0.85);
|
|
||||||
border-radius: 20px;
|
|
||||||
padding: 24px;
|
|
||||||
box-shadow: 0 8px 25px rgba(45, 80, 22, 0.08);
|
|
||||||
backdrop-filter: blur(10px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.header h1 {
|
|
||||||
font-size: 2.2rem;
|
|
||||||
color: #2d5016;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
font-weight: 700;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header p {
|
|
||||||
color: #5a7c65;
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 统计信息 */
|
|
||||||
.stats {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
||||||
gap: 15px;
|
|
||||||
margin-bottom: 25px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-card {
|
|
||||||
background: rgba(255, 255, 255, 0.9);
|
|
||||||
padding: 16px;
|
|
||||||
border-radius: 15px;
|
|
||||||
text-align: center;
|
|
||||||
box-shadow: 0 4px 18px rgba(45, 80, 22, 0.08);
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-number {
|
|
||||||
font-size: 1.8rem;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #1b5e20;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-label {
|
|
||||||
color: #5a7c65;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 游戏网格 */
|
|
||||||
.games-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
|
||||||
gap: 20px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 游戏卡片 */
|
|
||||||
.game-card {
|
|
||||||
background: rgba(255, 255, 255, 0.95);
|
|
||||||
border-radius: 18px;
|
|
||||||
overflow: hidden;
|
|
||||||
box-shadow: 0 6px 25px rgba(45, 80, 22, 0.1);
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.game-card:hover {
|
|
||||||
transform: translateY(-5px);
|
|
||||||
box-shadow: 0 12px 35px rgba(45, 80, 22, 0.15);
|
|
||||||
}
|
|
||||||
|
|
||||||
.game-cover {
|
|
||||||
width: 100%;
|
|
||||||
height: 180px;
|
|
||||||
object-fit: cover;
|
|
||||||
border-radius: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.game-info {
|
|
||||||
padding: 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.game-title {
|
|
||||||
font-size: 1.1rem;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #1b5e20;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
line-height: 1.3;
|
|
||||||
display: -webkit-box;
|
|
||||||
-webkit-line-clamp: 2;
|
|
||||||
-webkit-box-orient: vertical;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.game-description {
|
|
||||||
color: #5a7c65;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
display: -webkit-box;
|
|
||||||
-webkit-line-clamp: 3;
|
|
||||||
-webkit-box-orient: vertical;
|
|
||||||
overflow: hidden;
|
|
||||||
line-height: 1.4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.game-meta {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.game-price {
|
|
||||||
font-size: 1rem;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.original-price {
|
|
||||||
color: #81c784;
|
|
||||||
text-decoration: line-through;
|
|
||||||
}
|
|
||||||
|
|
||||||
.free-price {
|
|
||||||
color: #2e7d32;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.game-seller {
|
|
||||||
color: #5a7c65;
|
|
||||||
font-size: 0.85rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.game-dates {
|
|
||||||
background: rgba(129, 199, 132, 0.1);
|
|
||||||
padding: 10px;
|
|
||||||
border-radius: 10px;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
font-size: 0.85rem;
|
|
||||||
color: #2d5016;
|
|
||||||
}
|
|
||||||
|
|
||||||
.free-period {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.game-actions {
|
|
||||||
display: flex;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn {
|
|
||||||
flex: 1;
|
|
||||||
background: linear-gradient(135deg, #81c784 0%, #66bb6a 100%);
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
padding: 10px 16px;
|
|
||||||
border-radius: 10px;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
font-weight: 600;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.25s ease;
|
|
||||||
box-shadow: 0 4px 12px rgba(129, 199, 132, 0.35);
|
|
||||||
text-decoration: none;
|
|
||||||
text-align: center;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn:hover {
|
|
||||||
transform: translateY(-2px);
|
|
||||||
box-shadow: 0 6px 18px rgba(129, 199, 132, 0.45);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-secondary {
|
|
||||||
background: linear-gradient(135deg, #a5d6a7 0%, #81c784 100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 状态标签 */
|
|
||||||
.status-badge {
|
|
||||||
position: absolute;
|
|
||||||
top: 12px;
|
|
||||||
right: 12px;
|
|
||||||
padding: 6px 12px;
|
|
||||||
border-radius: 20px;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
font-weight: 600;
|
|
||||||
color: white;
|
|
||||||
text-shadow: 0 1px 2px rgba(0,0,0,0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-free {
|
|
||||||
background: linear-gradient(135deg, #4caf50 0%, #2e7d32 100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-upcoming {
|
|
||||||
background: linear-gradient(135deg, #ff9800 0%, #f57c00 100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 加载与错误 */
|
|
||||||
.loading, .error {
|
|
||||||
text-align: center;
|
|
||||||
padding: 40px;
|
|
||||||
background: rgba(255, 255, 255, 0.85);
|
|
||||||
border-radius: 15px;
|
|
||||||
box-shadow: 0 5px 20px rgba(45, 80, 22, 0.08);
|
|
||||||
}
|
|
||||||
|
|
||||||
.spinner {
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
border: 4px solid #e8f5e8;
|
|
||||||
border-top: 4px solid #81c784;
|
|
||||||
border-radius: 50%;
|
|
||||||
animation: spin 1s linear infinite;
|
|
||||||
margin: 0 auto 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes spin {
|
|
||||||
0% { transform: rotate(0deg); }
|
|
||||||
100% { transform: rotate(360deg); }
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 动画 */
|
|
||||||
.fade-in {
|
|
||||||
animation: fadeIn 0.6s ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes fadeIn {
|
|
||||||
from { opacity: 0; transform: translateY(20px); }
|
|
||||||
to { opacity: 1; transform: translateY(0); }
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 平板端适配 */
|
|
||||||
@media (max-width: 1024px) and (min-width: 768px) {
|
|
||||||
.container { padding: 16px; }
|
|
||||||
.header h1 { font-size: 2rem; }
|
|
||||||
.games-grid { grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); }
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 手机端优化 */
|
|
||||||
@media (max-width: 767px) {
|
|
||||||
.container { padding: 12px; }
|
|
||||||
.header { padding: 18px; }
|
|
||||||
.header h1 { font-size: 1.8rem; gap: 8px; }
|
|
||||||
|
|
||||||
.stats {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-card {
|
|
||||||
padding: 10px 8px;
|
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
.stat-number { font-size: 1.4rem; }
|
|
||||||
.stat-label { font-size: 0.75rem; }
|
|
||||||
|
|
||||||
.games-grid {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
gap: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.game-card { margin: 0 4px; }
|
|
||||||
.game-cover { height: 160px; }
|
|
||||||
.game-info { padding: 14px; }
|
|
||||||
|
|
||||||
.game-actions {
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn {
|
|
||||||
width: 100%;
|
|
||||||
padding: 12px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 小屏手机优化 */
|
|
||||||
@media (max-width: 480px) {
|
|
||||||
.stats {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
|
|
||||||
.game-meta {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
gap: 6px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 高分辨率显示器优化 */
|
|
||||||
@media (min-width: 1400px) {
|
|
||||||
.games-grid {
|
|
||||||
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="zh-CN">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
|
||||||
<title>Epic Games 免费游戏 - 60s API 集合</title>
|
|
||||||
<meta name="description" content="Epic Games 免费游戏列表,数据源自 60s.viki.moe,提供当前免费和即将免费的游戏信息。" />
|
|
||||||
|
|
||||||
<link rel="stylesheet" href="./css/style.css" />
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="container">
|
|
||||||
<header class="header">
|
|
||||||
<h1>
|
|
||||||
🎮 Epic Games 免费游戏
|
|
||||||
</h1>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<!-- 加载与错误状态 -->
|
|
||||||
<section id="loading" class="loading">
|
|
||||||
<div class="spinner"></div>
|
|
||||||
<p>正在加载游戏数据,请稍候…</p>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section id="error" class="error" style="display: none;">
|
|
||||||
<p>获取数据失败,请稍后重试</p>
|
|
||||||
<button id="refresh-btn" class="btn" style="margin-top: 15px;">重新加载</button>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<!-- 内容区域 -->
|
|
||||||
<main id="content" style="display: none;" class="fade-in">
|
|
||||||
<!-- 统计信息 -->
|
|
||||||
<div class="stats">
|
|
||||||
<div class="stat-card">
|
|
||||||
<div class="stat-number" id="total-games">0</div>
|
|
||||||
<div class="stat-label">总游戏数</div>
|
|
||||||
</div>
|
|
||||||
<div class="stat-card">
|
|
||||||
<div class="stat-number" id="free-now">0</div>
|
|
||||||
<div class="stat-label">当前免费</div>
|
|
||||||
</div>
|
|
||||||
<div class="stat-card">
|
|
||||||
<div class="stat-number" id="upcoming">0</div>
|
|
||||||
<div class="stat-label">即将免费</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 游戏列表 -->
|
|
||||||
<div class="games-grid" id="games-grid">
|
|
||||||
<!-- 游戏卡片将通过 JavaScript 动态生成 -->
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 刷新按钮 -->
|
|
||||||
<div style="text-align: center; margin-top: 30px;">
|
|
||||||
<button id="refresh-btn" class="btn btn-secondary">🔄 刷新数据</button>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src="./js/script.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,266 +0,0 @@
|
|||||||
// Epic Games 免费游戏 页面脚本
|
|
||||||
(function () {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const API = {
|
|
||||||
endpoints: [],
|
|
||||||
currentIndex: 0,
|
|
||||||
// 初始化API接口列表
|
|
||||||
async init() {
|
|
||||||
try {
|
|
||||||
const res = await fetch('./接口集合.json');
|
|
||||||
const endpoints = await res.json();
|
|
||||||
this.endpoints = endpoints.map(endpoint => `${endpoint}/v2/epic`);
|
|
||||||
} catch (e) {
|
|
||||||
// 如果无法加载接口集合,使用默认接口
|
|
||||||
this.endpoints = ['https://60s-api.viki.moe/v2/epic'];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// 获取当前接口URL
|
|
||||||
getCurrentUrl(encoding) {
|
|
||||||
if (this.endpoints.length === 0) return null;
|
|
||||||
const url = new URL(this.endpoints[this.currentIndex]);
|
|
||||||
if (encoding) url.searchParams.set('encoding', encoding);
|
|
||||||
return url.toString();
|
|
||||||
},
|
|
||||||
// 切换到下一个接口
|
|
||||||
switchToNext() {
|
|
||||||
this.currentIndex = (this.currentIndex + 1) % this.endpoints.length;
|
|
||||||
return this.currentIndex < this.endpoints.length;
|
|
||||||
},
|
|
||||||
// 重置到第一个接口
|
|
||||||
reset() {
|
|
||||||
this.currentIndex = 0;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// DOM 元素引用
|
|
||||||
const els = {
|
|
||||||
loading: null,
|
|
||||||
error: null,
|
|
||||||
container: null,
|
|
||||||
gamesGrid: null,
|
|
||||||
totalGames: null,
|
|
||||||
freeNow: null,
|
|
||||||
upcoming: null,
|
|
||||||
refreshBtn: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
function initDom() {
|
|
||||||
els.loading = document.getElementById('loading');
|
|
||||||
els.error = document.getElementById('error');
|
|
||||||
els.container = document.getElementById('content');
|
|
||||||
els.gamesGrid = document.getElementById('games-grid');
|
|
||||||
els.totalGames = document.getElementById('total-games');
|
|
||||||
els.freeNow = document.getElementById('free-now');
|
|
||||||
els.upcoming = document.getElementById('upcoming');
|
|
||||||
els.refreshBtn = document.getElementById('refresh-btn');
|
|
||||||
}
|
|
||||||
|
|
||||||
function showLoading() {
|
|
||||||
els.loading.style.display = 'block';
|
|
||||||
els.error.style.display = 'none';
|
|
||||||
els.container.style.display = 'none';
|
|
||||||
}
|
|
||||||
|
|
||||||
function showError(msg) {
|
|
||||||
els.loading.style.display = 'none';
|
|
||||||
els.error.style.display = 'block';
|
|
||||||
els.container.style.display = 'none';
|
|
||||||
els.error.querySelector('p').textContent = msg || '获取数据失败,请稍后重试';
|
|
||||||
}
|
|
||||||
|
|
||||||
function showContent() {
|
|
||||||
els.loading.style.display = 'none';
|
|
||||||
els.error.style.display = 'none';
|
|
||||||
els.container.style.display = 'block';
|
|
||||||
}
|
|
||||||
|
|
||||||
function safeText(text) {
|
|
||||||
const div = document.createElement('div');
|
|
||||||
div.textContent = text == null ? '' : String(text);
|
|
||||||
return div.innerHTML;
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatDate(dateStr) {
|
|
||||||
if (!dateStr) return '';
|
|
||||||
try {
|
|
||||||
const date = new Date(dateStr);
|
|
||||||
return date.toLocaleDateString('zh-CN', {
|
|
||||||
month: '2-digit',
|
|
||||||
day: '2-digit',
|
|
||||||
hour: '2-digit',
|
|
||||||
minute: '2-digit'
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
return dateStr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchData(preferLocal = false) {
|
|
||||||
if (preferLocal) {
|
|
||||||
try {
|
|
||||||
const res = await fetch('./返回接口.json', { cache: 'no-store' });
|
|
||||||
const json = await res.json();
|
|
||||||
return json;
|
|
||||||
} catch (e) {
|
|
||||||
throw new Error('本地数据加载失败');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 重置API索引到第一个接口
|
|
||||||
API.reset();
|
|
||||||
|
|
||||||
// 尝试所有API接口
|
|
||||||
for (let i = 0; i < API.endpoints.length; i++) {
|
|
||||||
try {
|
|
||||||
const url = API.getCurrentUrl();
|
|
||||||
console.log(`尝试接口 ${i + 1}/${API.endpoints.length}: ${url}`);
|
|
||||||
|
|
||||||
const res = await fetch(url, {
|
|
||||||
cache: 'no-store',
|
|
||||||
timeout: 10000 // 10秒超时
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!res.ok) {
|
|
||||||
throw new Error(`HTTP ${res.status}: ${res.statusText}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const json = await res.json();
|
|
||||||
|
|
||||||
if (json && json.code === 200) {
|
|
||||||
console.log(`接口 ${i + 1} 请求成功`);
|
|
||||||
return json;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error(json && json.message ? json.message : '接口返回异常');
|
|
||||||
|
|
||||||
} catch (e) {
|
|
||||||
console.warn(`接口 ${i + 1} 失败:`, e.message);
|
|
||||||
|
|
||||||
// 如果不是最后一个接口,切换到下一个
|
|
||||||
if (i < API.endpoints.length - 1) {
|
|
||||||
API.switchToNext();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 所有接口都失败了,尝试本地数据
|
|
||||||
console.warn('所有远程接口都失败,尝试本地数据');
|
|
||||||
try {
|
|
||||||
const res = await fetch('./返回接口.json', { cache: 'no-store' });
|
|
||||||
const json = await res.json();
|
|
||||||
return json;
|
|
||||||
} catch (e2) {
|
|
||||||
throw new Error('所有接口和本地数据都无法访问');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function createGameCard(game) {
|
|
||||||
const isFree = game.is_free_now;
|
|
||||||
const statusClass = isFree ? 'status-free' : 'status-upcoming';
|
|
||||||
const statusText = isFree ? '限时免费' : '即将免费';
|
|
||||||
|
|
||||||
return `
|
|
||||||
<div class="game-card fade-in">
|
|
||||||
<div class="status-badge ${statusClass}">${statusText}</div>
|
|
||||||
<img class="game-cover" src="${safeText(game.cover)}" alt="${safeText(game.title)} 封面" loading="lazy" />
|
|
||||||
<div class="game-info">
|
|
||||||
<h3 class="game-title">${safeText(game.title)}</h3>
|
|
||||||
<p class="game-description">${safeText(game.description)}</p>
|
|
||||||
|
|
||||||
<div class="game-meta">
|
|
||||||
<div class="game-price">
|
|
||||||
<span class="original-price">${safeText(game.original_price_desc)}</span>
|
|
||||||
<span class="free-price">免费</span>
|
|
||||||
</div>
|
|
||||||
<div class="game-seller">${safeText(game.seller)}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="game-dates">
|
|
||||||
<div class="free-period">
|
|
||||||
<span>开始:${formatDate(game.free_start)}</span>
|
|
||||||
<span>结束:${formatDate(game.free_end)}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="game-actions">
|
|
||||||
<a href="${safeText(game.link)}" target="_blank" class="btn">
|
|
||||||
${isFree ? '立即领取' : '查看详情'}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateStats(games) {
|
|
||||||
const total = games.length;
|
|
||||||
const freeNow = games.filter(game => game.is_free_now).length;
|
|
||||||
const upcoming = total - freeNow;
|
|
||||||
|
|
||||||
els.totalGames.textContent = total;
|
|
||||||
els.freeNow.textContent = freeNow;
|
|
||||||
els.upcoming.textContent = upcoming;
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderGames(games) {
|
|
||||||
if (!Array.isArray(games) || games.length === 0) {
|
|
||||||
els.gamesGrid.innerHTML = '<div style="grid-column: 1/-1; text-align: center; padding: 40px; color: #5a7c65;">暂无游戏数据</div>';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 按状态排序:免费的在前
|
|
||||||
const sortedGames = [...games].sort((a, b) => {
|
|
||||||
if (a.is_free_now && !b.is_free_now) return -1;
|
|
||||||
if (!a.is_free_now && b.is_free_now) return 1;
|
|
||||||
return 0;
|
|
||||||
});
|
|
||||||
|
|
||||||
const html = sortedGames.map(game => createGameCard(game)).join('');
|
|
||||||
els.gamesGrid.innerHTML = html;
|
|
||||||
|
|
||||||
updateStats(games);
|
|
||||||
}
|
|
||||||
|
|
||||||
function render(data) {
|
|
||||||
const games = data?.data || [];
|
|
||||||
renderGames(games);
|
|
||||||
showContent();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function load() {
|
|
||||||
showLoading();
|
|
||||||
|
|
||||||
// 初始化API接口列表
|
|
||||||
await API.init();
|
|
||||||
|
|
||||||
try {
|
|
||||||
const data = await fetchData(false);
|
|
||||||
render(data);
|
|
||||||
} catch (e) {
|
|
||||||
console.error('数据获取失败:', e);
|
|
||||||
showError(e.message || '获取数据失败,请稍后重试');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function bindEvents() {
|
|
||||||
if (els.refreshBtn) {
|
|
||||||
els.refreshBtn.addEventListener('click', load);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 快捷键 Ctrl+R 刷新(不拦截浏览器默认刷新)
|
|
||||||
document.addEventListener('keydown', (e) => {
|
|
||||||
if (e.ctrlKey && e.key === 'r' && !e.defaultPrevented) {
|
|
||||||
// 不阻止默认行为,让浏览器正常刷新
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
|
||||||
initDom();
|
|
||||||
bindEvents();
|
|
||||||
load();
|
|
||||||
});
|
|
||||||
})();
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
[
|
|
||||||
"https://60s-cf.viki.moe",
|
|
||||||
"https://60s.viki.moe",
|
|
||||||
"https://60s.b23.run",
|
|
||||||
"https://60s.114128.xyz",
|
|
||||||
"https://60s-cf.114128.xyz"
|
|
||||||
]
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
{
|
|
||||||
"code": 200,
|
|
||||||
"message": "获取成功。数据来自官方/权威源头,以确保稳定与实时。开源地址 https://github.com/vikiboss/60s,反馈群 595941841",
|
|
||||||
"data": [
|
|
||||||
{
|
|
||||||
"id": "9aa227e2ba294bb1a95c95fde892eb31",
|
|
||||||
"title": "《Totally Reliable Delivery Service》 Standard Edition",
|
|
||||||
"cover": "https://cdn1.epicgames.com/52b90f9a982a404781b189f6a7903226/offer/EGS_TotallyReliableDeliveryService_WereFiveGames_S1-2560x1440-47e6e9562d62705a75ea7b7096d0b8dc.jpg",
|
|
||||||
"original_price": 52,
|
|
||||||
"original_price_desc": "¥52.00",
|
|
||||||
"description": "穿好护腰护具,发动货车,送货的时间到啦!在一个高度互动的沙盒世界中,与最多三位好友一起随意地完成送货。货物已试投,这就是我们靠谱快递(Totally Reliable Delivery Service)的品质保证!",
|
|
||||||
"seller": "Infogrames LLC",
|
|
||||||
"is_free_now": true,
|
|
||||||
"free_start": "2025/08/14 23:00:00",
|
|
||||||
"free_start_at": 1755183600000,
|
|
||||||
"free_end": "2025/08/21 23:00:00",
|
|
||||||
"free_end_at": 1755788400000,
|
|
||||||
"link": "https://store.epicgames.com/store/zh-CN/p/totally-reliable-delivery-service/home"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "8ea3500dc38e4f429702bf889c172d3d",
|
|
||||||
"title": "Hidden Folks",
|
|
||||||
"cover": "https://cdn1.epicgames.com/spt-assets/7bfd56b0586348dcb139945d9e59f988/hidden-folks-1b7hh.png",
|
|
||||||
"original_price": 47,
|
|
||||||
"original_price_desc": "¥47.00",
|
|
||||||
"description": "Search for hidden folks in hand-drawn, interactive, miniature landscapes. Unfurl tent flaps, cut through bushes, slam doors, and poke some crocodiles! Rooooaaaarrrr!!!!!",
|
|
||||||
"seller": "Adriaan de Jongh",
|
|
||||||
"is_free_now": true,
|
|
||||||
"free_start": "2025/08/14 23:00:00",
|
|
||||||
"free_start_at": 1755183600000,
|
|
||||||
"free_end": "2025/08/21 23:00:00",
|
|
||||||
"free_end_at": 1755788400000,
|
|
||||||
"link": "https://store.epicgames.com/store/zh-CN/p/hidden-folks-239d16"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "4cbb6c3704d240f19c3dd5f5cb2b0cb4",
|
|
||||||
"title": "Kamaeru",
|
|
||||||
"cover": "https://cdn1.epicgames.com/spt-assets/44313cfbb62b4df5801d0c8d541c2624/kamaeru-40asc.png",
|
|
||||||
"original_price": 62,
|
|
||||||
"original_price_desc": "¥62.00",
|
|
||||||
"description": "Foster a sanctuary for frogs and restore the biodiversity of the wetlands in Kamaeru, a cozy frog collecting game, where you take pictures of frogs, play mini-games and decorate your habitat. Hop right to it!",
|
|
||||||
"seller": "Armor Games Studios",
|
|
||||||
"is_free_now": false,
|
|
||||||
"free_start": "2025/08/21 23:00:00",
|
|
||||||
"free_start_at": 1755788400000,
|
|
||||||
"free_end": "2025/08/28 23:00:00",
|
|
||||||
"free_end_at": 1756393200000,
|
|
||||||
"link": "https://store.epicgames.com/store/zh-CN/p/kamaeru-0c301e"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "0d9a533f0e684cc18620a8f408e8e72c",
|
|
||||||
"title": "Strange Horticulture",
|
|
||||||
"cover": "https://cdn1.epicgames.com/spt-assets/15e8e3eba65a4763a815d6eae1d763b2/strange-horticulture-offer-2wghv.png",
|
|
||||||
"original_price": 45,
|
|
||||||
"original_price_desc": "¥45.00",
|
|
||||||
"description": "款神秘学解谜游戏,你将扮演当地植物商店的店主,寻找并识别新的植物,悠闲撸猫,与女巫团体交谈,或加入异教。收集各种强大的植物,用它们来影响故事走向,揭开昂德米尔镇的黑暗谜团。",
|
|
||||||
"seller": "Iceberg Interactive",
|
|
||||||
"is_free_now": false,
|
|
||||||
"free_start": "2025/08/21 23:00:00",
|
|
||||||
"free_start_at": 1755788400000,
|
|
||||||
"free_end": "2025/08/28 23:00:00",
|
|
||||||
"free_end_at": 1756393200000,
|
|
||||||
"link": "https://store.epicgames.com/store/zh-CN/p/strange-horticulture-360e80"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
/* 玻璃拟态背景相关样式 */
|
|
||||||
body {
|
|
||||||
background: linear-gradient(135deg,
|
|
||||||
#667eea 0%,
|
|
||||||
#764ba2 25%,
|
|
||||||
#f093fb 50%,
|
|
||||||
#f5576c 75%,
|
|
||||||
#4facfe 100%
|
|
||||||
);
|
|
||||||
background-size: 400% 400%;
|
|
||||||
animation: gradientShift 20s ease infinite;
|
|
||||||
background-attachment: fixed;
|
|
||||||
min-height: 100vh;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes gradientShift {
|
|
||||||
0% { background-position: 0% 50%; }
|
|
||||||
25% { background-position: 100% 50%; }
|
|
||||||
50% { background-position: 100% 100%; }
|
|
||||||
75% { background-position: 0% 100%; }
|
|
||||||
100% { background-position: 0% 50%; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 玻璃拟态装饰层 */
|
|
||||||
body::before {
|
|
||||||
content: '';
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
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%);
|
|
||||||
pointer-events: none;
|
|
||||||
z-index: -1;
|
|
||||||
animation: glassFloat 25s ease-in-out infinite alternate;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 毛玻璃气泡效果 */
|
|
||||||
body::after {
|
|
||||||
content: '';
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
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;
|
|
||||||
pointer-events: none;
|
|
||||||
z-index: -1;
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes glassFloat {
|
|
||||||
0% {
|
|
||||||
transform: translateY(0px) rotate(0deg);
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
transform: translateY(-20px) rotate(2deg);
|
|
||||||
opacity: 0.9;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes bubbleFloat {
|
|
||||||
0%, 100% {
|
|
||||||
transform: translateX(0) translateY(0);
|
|
||||||
}
|
|
||||||
25% {
|
|
||||||
transform: translateX(-15px) translateY(-10px);
|
|
||||||
}
|
|
||||||
50% {
|
|
||||||
transform: translateX(10px) translateY(-20px);
|
|
||||||
}
|
|
||||||
75% {
|
|
||||||
transform: translateX(-5px) translateY(-15px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,71 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="zh-CN">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>🌙 农历信息查询</title>
|
|
||||||
<link rel="stylesheet" href="css/background.css">
|
|
||||||
<link rel="stylesheet" href="css/style.css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="container">
|
|
||||||
<header class="header">
|
|
||||||
<div class="header-icon">🏮</div>
|
|
||||||
<h1 class="title">🌙 农历信息查询 📅</h1>
|
|
||||||
<p class="subtitle">传统文化 · 时光转换 · 节气查询</p>
|
|
||||||
|
|
||||||
<div class="date-selector">
|
|
||||||
<div class="input-group">
|
|
||||||
<label for="dateInput" class="input-label">
|
|
||||||
<span class="label-icon">📅</span>
|
|
||||||
选择日期
|
|
||||||
</label>
|
|
||||||
<input type="date" id="dateInput" class="date-input" />
|
|
||||||
</div>
|
|
||||||
<button id="queryBtn" class="query-btn">
|
|
||||||
<span class="btn-icon">🔍</span>
|
|
||||||
查询农历
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="update-time">
|
|
||||||
<span class="time-icon">⏰</span>
|
|
||||||
<span id="updateTime">等待查询...</span>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<div class="loading" id="loading" style="display: none;">
|
|
||||||
<div class="loading-content">
|
|
||||||
<div class="glass-spinner"></div>
|
|
||||||
<div class="loading-text">
|
|
||||||
<span class="loading-emoji">🔮</span>
|
|
||||||
<p>正在查询农历信息...</p>
|
|
||||||
<div class="loading-dots">
|
|
||||||
<span></span>
|
|
||||||
<span></span>
|
|
||||||
<span></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="lunar-info" id="lunarInfo" style="display: none;">
|
|
||||||
<!-- 农历信息将动态生成 -->
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="error-message" id="errorMessage" style="display: none;">
|
|
||||||
<div class="error-content">
|
|
||||||
<div class="error-icon">😔</div>
|
|
||||||
<h3>查询失败了</h3>
|
|
||||||
<p>无法获取农历信息,请稍后重试</p>
|
|
||||||
<button onclick="queryLunarInfo()" class="retry-btn">
|
|
||||||
<span>🔄</span>
|
|
||||||
重新查询
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<script src="js/script.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,485 +0,0 @@
|
|||||||
// API接口列表
|
|
||||||
const API_ENDPOINTS = [
|
|
||||||
"https://60s-cf.viki.moe",
|
|
||||||
"https://60s.viki.moe",
|
|
||||||
"https://60s.b23.run",
|
|
||||||
"https://60s.114128.xyz",
|
|
||||||
"https://60s-cf.114128.xyz"
|
|
||||||
];
|
|
||||||
|
|
||||||
// 当前使用的API索引
|
|
||||||
let currentApiIndex = 0;
|
|
||||||
|
|
||||||
// DOM元素
|
|
||||||
const loadingElement = document.getElementById('loading');
|
|
||||||
const lunarInfoElement = document.getElementById('lunarInfo');
|
|
||||||
const errorMessageElement = document.getElementById('errorMessage');
|
|
||||||
const updateTimeElement = document.getElementById('updateTime');
|
|
||||||
const dateInput = document.getElementById('dateInput');
|
|
||||||
const queryBtn = document.getElementById('queryBtn');
|
|
||||||
|
|
||||||
// 页面加载完成后初始化
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
|
||||||
initializePage();
|
|
||||||
});
|
|
||||||
|
|
||||||
// 初始化页面
|
|
||||||
function initializePage() {
|
|
||||||
// 设置默认日期为今天
|
|
||||||
const today = new Date();
|
|
||||||
const dateString = today.toISOString().split('T')[0];
|
|
||||||
dateInput.value = dateString;
|
|
||||||
|
|
||||||
// 绑定事件
|
|
||||||
queryBtn.addEventListener('click', queryLunarInfo);
|
|
||||||
dateInput.addEventListener('change', queryLunarInfo);
|
|
||||||
|
|
||||||
// 自动查询当天信息
|
|
||||||
queryLunarInfo();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 查询农历信息
|
|
||||||
async function queryLunarInfo() {
|
|
||||||
const selectedDate = dateInput.value;
|
|
||||||
if (!selectedDate) {
|
|
||||||
showError('请选择查询日期');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
showLoading();
|
|
||||||
hideError();
|
|
||||||
hideLunarInfo();
|
|
||||||
|
|
||||||
try {
|
|
||||||
const data = await fetchLunarData(selectedDate);
|
|
||||||
displayLunarInfo(data.data);
|
|
||||||
updateQueryTime();
|
|
||||||
} catch (error) {
|
|
||||||
console.error('查询失败:', error);
|
|
||||||
showError('查询农历信息失败,请稍后重试');
|
|
||||||
}
|
|
||||||
|
|
||||||
hideLoading();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取农历数据
|
|
||||||
async function fetchLunarData(date) {
|
|
||||||
for (let i = 0; i < API_ENDPOINTS.length; i++) {
|
|
||||||
const apiUrl = API_ENDPOINTS[currentApiIndex];
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch(`${apiUrl}/v2/lunar?date=${date}`, {
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
'Accept': 'application/json',
|
|
||||||
},
|
|
||||||
timeout: 10000
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`HTTP ${response.status}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
if (data.code === 200 && data.data) {
|
|
||||||
return data;
|
|
||||||
} else {
|
|
||||||
throw new Error('数据格式错误');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`API ${apiUrl} 请求失败:`, error);
|
|
||||||
currentApiIndex = (currentApiIndex + 1) % API_ENDPOINTS.length;
|
|
||||||
|
|
||||||
if (i === API_ENDPOINTS.length - 1) {
|
|
||||||
throw new Error('所有API接口都无法访问');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 显示农历信息
|
|
||||||
function displayLunarInfo(lunarData) {
|
|
||||||
lunarInfoElement.innerHTML = `
|
|
||||||
<div class="info-card">
|
|
||||||
<div class="card-header">
|
|
||||||
<div class="card-icon">📅</div>
|
|
||||||
<div class="card-title">公历信息</div>
|
|
||||||
</div>
|
|
||||||
<div class="card-content">
|
|
||||||
<div class="info-item">
|
|
||||||
<div class="item-icon">🗓️</div>
|
|
||||||
<div class="item-label">公历日期</div>
|
|
||||||
<div class="item-value">${lunarData.solar.year}年${String(lunarData.solar.month).padStart(2, '0')}月${String(lunarData.solar.day).padStart(2, '0')}日</div>
|
|
||||||
</div>
|
|
||||||
<div class="info-item">
|
|
||||||
<div class="item-icon">🌍</div>
|
|
||||||
<div class="item-label">星期</div>
|
|
||||||
<div class="item-value">${lunarData.solar.week_desc}</div>
|
|
||||||
</div>
|
|
||||||
<div class="info-item">
|
|
||||||
<div class="item-icon"><3E></div>
|
|
||||||
<div class="item-label">季节</div>
|
|
||||||
<div class="item-value">${lunarData.solar.season_name_desc}</div>
|
|
||||||
</div>
|
|
||||||
<div class="info-item">
|
|
||||||
<div class="item-icon">⭐</div>
|
|
||||||
<div class="item-label">星座</div>
|
|
||||||
<div class="item-value">${lunarData.constellation.name}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="info-card">
|
|
||||||
<div class="card-header">
|
|
||||||
<div class="card-icon">🌙</div>
|
|
||||||
<div class="card-title">农历信息</div>
|
|
||||||
</div>
|
|
||||||
<div class="card-content">
|
|
||||||
<div class="info-item">
|
|
||||||
<div class="item-icon">🏮</div>
|
|
||||||
<div class="item-label">农历日期</div>
|
|
||||||
<div class="item-value">${lunarData.lunar.desc_short}</div>
|
|
||||||
</div>
|
|
||||||
<div class="info-item">
|
|
||||||
<div class="item-icon">🐲</div>
|
|
||||||
<div class="item-label">生肖年</div>
|
|
||||||
<div class="item-value">${lunarData.zodiac.year}年</div>
|
|
||||||
</div>
|
|
||||||
<div class="info-item">
|
|
||||||
<div class="item-icon">⚡</div>
|
|
||||||
<div class="item-label">天干地支</div>
|
|
||||||
<div class="item-value">${lunarData.sixty_cycle.year.name}</div>
|
|
||||||
</div>
|
|
||||||
<div class="info-item">
|
|
||||||
<div class="item-icon">🌙</div>
|
|
||||||
<div class="item-label">月相</div>
|
|
||||||
<div class="item-value">${lunarData.phase.name}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="info-card">
|
|
||||||
<div class="card-header">
|
|
||||||
<div class="card-icon">🌸</div>
|
|
||||||
<div class="card-title">节气节日</div>
|
|
||||||
</div>
|
|
||||||
<div class="card-content">
|
|
||||||
<div class="info-item">
|
|
||||||
<div class="item-icon">🍃</div>
|
|
||||||
<div class="item-label">当前节气</div>
|
|
||||||
<div class="item-value">${lunarData.term.stage ? lunarData.term.stage.name : '无节气'}</div>
|
|
||||||
</div>
|
|
||||||
<div class="info-item">
|
|
||||||
<div class="item-icon">🎊</div>
|
|
||||||
<div class="item-label">法定假日</div>
|
|
||||||
<div class="item-value">${lunarData.legal_holiday ? lunarData.legal_holiday.name : '无假日'}</div>
|
|
||||||
</div>
|
|
||||||
<div class="info-item">
|
|
||||||
<div class="item-icon"><3E></div>
|
|
||||||
<div class="item-label">传统节日</div>
|
|
||||||
<div class="item-value">${lunarData.festival.both_desc || '无特殊节日'}</div>
|
|
||||||
</div>
|
|
||||||
<div class="info-item">
|
|
||||||
<div class="item-icon">🔢</div>
|
|
||||||
<div class="item-label">一年第几天</div>
|
|
||||||
<div class="item-value">第${lunarData.stats.day_of_year}天</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="info-card">
|
|
||||||
<div class="card-header">
|
|
||||||
<div class="card-icon">⏰</div>
|
|
||||||
<div class="card-title">时辰干支</div>
|
|
||||||
</div>
|
|
||||||
<div class="card-content">
|
|
||||||
<div class="info-item">
|
|
||||||
<div class="item-icon">🕐</div>
|
|
||||||
<div class="item-label">当前时辰</div>
|
|
||||||
<div class="item-value">${lunarData.lunar.hour_desc}</div>
|
|
||||||
</div>
|
|
||||||
<div class="info-item">
|
|
||||||
<div class="item-icon">⚡</div>
|
|
||||||
<div class="item-label">时辰干支</div>
|
|
||||||
<div class="item-value">${lunarData.sixty_cycle.hour.name}</div>
|
|
||||||
</div>
|
|
||||||
<div class="info-item">
|
|
||||||
<div class="item-icon">🐓</div>
|
|
||||||
<div class="item-label">时辰生肖</div>
|
|
||||||
<div class="item-value">${lunarData.zodiac.hour}</div>
|
|
||||||
</div>
|
|
||||||
<div class="info-item">
|
|
||||||
<div class="item-icon">🎵</div>
|
|
||||||
<div class="item-label">纳音</div>
|
|
||||||
<div class="item-value">${lunarData.nayin.hour}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="info-card">
|
|
||||||
<div class="card-header">
|
|
||||||
<div class="card-icon">🔮</div>
|
|
||||||
<div class="card-title">黄历宜忌</div>
|
|
||||||
</div>
|
|
||||||
<div class="card-content">
|
|
||||||
<div class="info-item">
|
|
||||||
<div class="item-icon">✅</div>
|
|
||||||
<div class="item-label">宜</div>
|
|
||||||
<div class="item-value">${formatTabooText(lunarData.taboo.day.recommends)}</div>
|
|
||||||
</div>
|
|
||||||
<div class="info-item">
|
|
||||||
<div class="item-icon">❌</div>
|
|
||||||
<div class="item-label">忌</div>
|
|
||||||
<div class="item-value">${formatTabooText(lunarData.taboo.day.avoids)}</div>
|
|
||||||
</div>
|
|
||||||
<div class="info-item">
|
|
||||||
<div class="item-icon">🕐</div>
|
|
||||||
<div class="item-label">时辰宜</div>
|
|
||||||
<div class="item-value">${formatTabooText(lunarData.taboo.hour.recommends)}</div>
|
|
||||||
</div>
|
|
||||||
<div class="info-item">
|
|
||||||
<div class="item-icon">🚫</div>
|
|
||||||
<div class="item-label">时辰忌</div>
|
|
||||||
<div class="item-value">${formatTabooText(lunarData.taboo.hour.avoids)}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="info-card">
|
|
||||||
<div class="card-header">
|
|
||||||
<div class="card-icon">🌟</div>
|
|
||||||
<div class="card-title">运势财运</div>
|
|
||||||
</div>
|
|
||||||
<div class="card-content">
|
|
||||||
<div class="info-item">
|
|
||||||
<div class="item-icon">🎯</div>
|
|
||||||
<div class="item-label">今日运势</div>
|
|
||||||
<div class="item-value">${lunarData.fortune.today_luck}</div>
|
|
||||||
</div>
|
|
||||||
<div class="info-item">
|
|
||||||
<div class="item-icon">💼</div>
|
|
||||||
<div class="item-label">事业运</div>
|
|
||||||
<div class="item-value">${lunarData.fortune.career}</div>
|
|
||||||
</div>
|
|
||||||
<div class="info-item">
|
|
||||||
<div class="item-icon">💰</div>
|
|
||||||
<div class="item-label">财运</div>
|
|
||||||
<div class="item-value">${lunarData.fortune.money}</div>
|
|
||||||
</div>
|
|
||||||
<div class="info-item">
|
|
||||||
<div class="item-icon">💕</div>
|
|
||||||
<div class="item-label">感情运</div>
|
|
||||||
<div class="item-value">${lunarData.fortune.love}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="info-card">
|
|
||||||
<div class="card-header">
|
|
||||||
<div class="card-icon">📊</div>
|
|
||||||
<div class="card-title">年度统计</div>
|
|
||||||
</div>
|
|
||||||
<div class="card-content">
|
|
||||||
<div class="info-item">
|
|
||||||
<div class="item-icon">📈</div>
|
|
||||||
<div class="item-label">年度进度</div>
|
|
||||||
<div class="item-value">${lunarData.stats.percents_formatted.year}</div>
|
|
||||||
</div>
|
|
||||||
<div class="info-item">
|
|
||||||
<div class="item-icon">📅</div>
|
|
||||||
<div class="item-label">本月进度</div>
|
|
||||||
<div class="item-value">${lunarData.stats.percents_formatted.month}</div>
|
|
||||||
</div>
|
|
||||||
<div class="info-item">
|
|
||||||
<div class="item-icon">🗓️</div>
|
|
||||||
<div class="item-label">本周第几天</div>
|
|
||||||
<div class="item-value">第${lunarData.stats.week_of_month}周</div>
|
|
||||||
</div>
|
|
||||||
<div class="info-item">
|
|
||||||
<div class="item-icon">⏰</div>
|
|
||||||
<div class="item-label">今日进度</div>
|
|
||||||
<div class="item-value">${lunarData.stats.percents_formatted.day}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
${generateHourlyTaboo(lunarData.taboo.hours)}
|
|
||||||
`;
|
|
||||||
|
|
||||||
showLunarInfo();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 格式化宜忌文本
|
|
||||||
function formatTabooText(text) {
|
|
||||||
if (!text) return '无';
|
|
||||||
return text.replace(/\./g, '、');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 生成十二时辰宜忌
|
|
||||||
function generateHourlyTaboo(hours) {
|
|
||||||
if (!hours || hours.length === 0) return '';
|
|
||||||
|
|
||||||
const hourCards = hours.map(hour => `
|
|
||||||
<div class="hour-item">
|
|
||||||
<div class="hour-name">${hour.hour}</div>
|
|
||||||
<div class="hour-content">
|
|
||||||
<div class="hour-recommends">
|
|
||||||
<span class="hour-label">宜:</span>
|
|
||||||
<span class="hour-text">${formatTabooText(hour.recommends) || '无'}</span>
|
|
||||||
</div>
|
|
||||||
<div class="hour-avoids">
|
|
||||||
<span class="hour-label">忌:</span>
|
|
||||||
<span class="hour-text">${formatTabooText(hour.avoids) || '无'}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`).join('');
|
|
||||||
|
|
||||||
return `
|
|
||||||
<div class="info-card hours-card">
|
|
||||||
<div class="card-header">
|
|
||||||
<div class="card-icon">⏰</div>
|
|
||||||
<div class="card-title">十二时辰宜忌</div>
|
|
||||||
</div>
|
|
||||||
<div class="card-content">
|
|
||||||
<div class="hours-grid">
|
|
||||||
${hourCards}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新查询时间
|
|
||||||
function updateQueryTime() {
|
|
||||||
const now = new Date();
|
|
||||||
const timeStr = now.toLocaleString('zh-CN', {
|
|
||||||
year: 'numeric',
|
|
||||||
month: '2-digit',
|
|
||||||
day: '2-digit',
|
|
||||||
hour: '2-digit',
|
|
||||||
minute: '2-digit',
|
|
||||||
second: '2-digit'
|
|
||||||
});
|
|
||||||
updateTimeElement.textContent = `查询时间: ${timeStr}`;
|
|
||||||
|
|
||||||
// 添加成功提示
|
|
||||||
showSuccessMessage('🌙 农历信息已更新');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 显示成功消息
|
|
||||||
function showSuccessMessage(message) {
|
|
||||||
// 移除之前的提示
|
|
||||||
const existingToast = document.querySelector('.success-toast');
|
|
||||||
if (existingToast) {
|
|
||||||
existingToast.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
const toast = document.createElement('div');
|
|
||||||
toast.className = 'success-toast';
|
|
||||||
toast.textContent = message;
|
|
||||||
toast.style.cssText = `
|
|
||||||
position: fixed;
|
|
||||||
top: 20px;
|
|
||||||
right: 20px;
|
|
||||||
background: rgba(255, 255, 255, 0.2);
|
|
||||||
backdrop-filter: blur(15px);
|
|
||||||
-webkit-backdrop-filter: blur(15px);
|
|
||||||
color: rgba(255, 255, 255, 0.95);
|
|
||||||
padding: 12px 20px;
|
|
||||||
border-radius: 25px;
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
|
||||||
box-shadow: 0 8px 32px rgba(31, 38, 135, 0.2);
|
|
||||||
z-index: 1000;
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 0.9em;
|
|
||||||
animation: glassToastSlide 0.5s ease-out;
|
|
||||||
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
|
|
||||||
`;
|
|
||||||
|
|
||||||
document.body.appendChild(toast);
|
|
||||||
|
|
||||||
// 3秒后自动移除
|
|
||||||
setTimeout(() => {
|
|
||||||
toast.style.animation = 'glassToastSlideOut 0.5s ease-in forwards';
|
|
||||||
setTimeout(() => toast.remove(), 500);
|
|
||||||
}, 3000);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 显示加载状态
|
|
||||||
function showLoading() {
|
|
||||||
loadingElement.style.display = 'block';
|
|
||||||
}
|
|
||||||
|
|
||||||
// 隐藏加载状态
|
|
||||||
function hideLoading() {
|
|
||||||
loadingElement.style.display = 'none';
|
|
||||||
}
|
|
||||||
|
|
||||||
// 显示农历信息
|
|
||||||
function showLunarInfo() {
|
|
||||||
lunarInfoElement.style.display = 'block';
|
|
||||||
}
|
|
||||||
|
|
||||||
// 隐藏农历信息
|
|
||||||
function hideLunarInfo() {
|
|
||||||
lunarInfoElement.style.display = 'none';
|
|
||||||
}
|
|
||||||
|
|
||||||
// 显示错误信息
|
|
||||||
function showError(message = '查询失败,请稍后重试') {
|
|
||||||
errorMessageElement.style.display = 'block';
|
|
||||||
const errorContent = errorMessageElement.querySelector('.error-content p');
|
|
||||||
if (errorContent) {
|
|
||||||
errorContent.textContent = message;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 隐藏错误信息
|
|
||||||
function hideError() {
|
|
||||||
errorMessageElement.style.display = 'none';
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加CSS动画到页面
|
|
||||||
if (!document.querySelector('#toast-styles')) {
|
|
||||||
const style = document.createElement('style');
|
|
||||||
style.id = 'toast-styles';
|
|
||||||
style.textContent = `
|
|
||||||
@keyframes glassToastSlide {
|
|
||||||
from {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateX(100px) scale(0.8);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
opacity: 1;
|
|
||||||
transform: translateX(0) scale(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes glassToastSlideOut {
|
|
||||||
from {
|
|
||||||
opacity: 1;
|
|
||||||
transform: translateX(0) scale(1);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateX(100px) scale(0.8);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
document.head.appendChild(style);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 键盘快捷键支持
|
|
||||||
document.addEventListener('keydown', function(e) {
|
|
||||||
if (e.key === 'Enter') {
|
|
||||||
e.preventDefault();
|
|
||||||
queryLunarInfo();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e.key === 'r' && (e.ctrlKey || e.metaKey)) {
|
|
||||||
e.preventDefault();
|
|
||||||
queryLunarInfo();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
[
|
|
||||||
"https://60s-cf.viki.moe",
|
|
||||||
"https://60s.viki.moe",
|
|
||||||
"https://60s.b23.run",
|
|
||||||
"https://60s.114128.xyz",
|
|
||||||
"https://60s-cf.114128.xyz"
|
|
||||||
]
|
|
||||||
@@ -1,647 +0,0 @@
|
|||||||
{
|
|
||||||
"code": 200,
|
|
||||||
"message": "获取成功。数据来自官方/权威源头,以确保稳定与实时。开源地址 https://github.com/vikiboss/60s,反馈群 595941841",
|
|
||||||
"data": {
|
|
||||||
"solar": {
|
|
||||||
"year": 2025,
|
|
||||||
"month": 9,
|
|
||||||
"day": 1,
|
|
||||||
"hour": 17,
|
|
||||||
"minute": 58,
|
|
||||||
"second": 47,
|
|
||||||
"full": "2025-09-01",
|
|
||||||
"full_with_time": "2025-09-01 17:58:47",
|
|
||||||
"week": 1,
|
|
||||||
"week_desc": "星期一",
|
|
||||||
"week_desc_short": "一",
|
|
||||||
"season": 3,
|
|
||||||
"season_desc": "三季度",
|
|
||||||
"season_desc_short": "三",
|
|
||||||
"season_name": "秋",
|
|
||||||
"season_name_desc": "秋天",
|
|
||||||
"is_leap_year": false
|
|
||||||
},
|
|
||||||
"lunar": {
|
|
||||||
"year": "乙巳",
|
|
||||||
"month": "七",
|
|
||||||
"day": "初十",
|
|
||||||
"hour": "酉",
|
|
||||||
"full_with_hour": "农历乙巳年七月初十酉时",
|
|
||||||
"desc_short": "农历乙巳年七月初十",
|
|
||||||
"year_desc": "农历乙巳年",
|
|
||||||
"month_desc": "七月",
|
|
||||||
"day_desc": "初十",
|
|
||||||
"hour_desc": "酉时",
|
|
||||||
"is_leap_month": false
|
|
||||||
},
|
|
||||||
"stats": {
|
|
||||||
"day_of_year": 244,
|
|
||||||
"week_of_year": 36,
|
|
||||||
"week_of_month": 1,
|
|
||||||
"percents": {
|
|
||||||
"year": 0.665753424657534,
|
|
||||||
"month": 0.0333333333333333,
|
|
||||||
"week": 0.142857142857143,
|
|
||||||
"day": 0.749161909722222
|
|
||||||
},
|
|
||||||
"percents_formatted": {
|
|
||||||
"year": "66.58%",
|
|
||||||
"month": "3.33%",
|
|
||||||
"week": "14.29%",
|
|
||||||
"day": "74.92%"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"term": {
|
|
||||||
"today": null,
|
|
||||||
"stage": {
|
|
||||||
"name": "处暑",
|
|
||||||
"position": 10,
|
|
||||||
"is_jie": false,
|
|
||||||
"is_qi": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"zodiac": {
|
|
||||||
"year": "蛇",
|
|
||||||
"month": "鸡",
|
|
||||||
"day": "鸡",
|
|
||||||
"hour": "鸡"
|
|
||||||
},
|
|
||||||
"sixty_cycle": {
|
|
||||||
"year": {
|
|
||||||
"heaven_stem": "乙",
|
|
||||||
"earth_branch": "巳",
|
|
||||||
"name": "乙巳年",
|
|
||||||
"name_short": "乙巳"
|
|
||||||
},
|
|
||||||
"month": {
|
|
||||||
"heaven_stem": "乙",
|
|
||||||
"earth_branch": "酉",
|
|
||||||
"name": "乙酉月",
|
|
||||||
"name_short": "乙酉"
|
|
||||||
},
|
|
||||||
"day": {
|
|
||||||
"heaven_stem": "癸",
|
|
||||||
"earth_branch": "酉",
|
|
||||||
"name": "癸酉日",
|
|
||||||
"name_short": "癸酉"
|
|
||||||
},
|
|
||||||
"hour": {
|
|
||||||
"heaven_stem": "辛",
|
|
||||||
"earth_branch": "酉",
|
|
||||||
"name": "辛酉时",
|
|
||||||
"name_short": "辛酉"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"legal_holiday": null,
|
|
||||||
"festival": {
|
|
||||||
"solar": null,
|
|
||||||
"lunar": null,
|
|
||||||
"both_desc": null
|
|
||||||
},
|
|
||||||
"phase": {
|
|
||||||
"name": "宵月",
|
|
||||||
"position": 10
|
|
||||||
},
|
|
||||||
"constellation": {
|
|
||||||
"name": "处女座",
|
|
||||||
"name_short": "处女"
|
|
||||||
},
|
|
||||||
"taboo": {
|
|
||||||
"day": {
|
|
||||||
"recommends": "解除.祭祀.祈福.求嗣.修造.动土.竖柱.上梁.安床.纳畜.盖屋.合脊.起基.入殓.破土.安葬",
|
|
||||||
"avoids": "出火.嫁娶.开光.进人口.出行.词讼.开市.入宅.移徙.赴任"
|
|
||||||
},
|
|
||||||
"hour": {
|
|
||||||
"hour": "酉时",
|
|
||||||
"hour_short": "酉",
|
|
||||||
"avoids": "乘船.造桥",
|
|
||||||
"recommends": "嫁娶.出行.移徙.入宅.开市.赴任.祈福.安床.开仓.盖屋.修造.求财"
|
|
||||||
},
|
|
||||||
"hours": [
|
|
||||||
{
|
|
||||||
"hour": "酉时",
|
|
||||||
"hour_short": "酉",
|
|
||||||
"recommends": "嫁娶.出行.移徙.入宅.开市.赴任.祈福.安床.开仓.盖屋.修造.求财",
|
|
||||||
"avoids": "乘船.造桥"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"hour": "戌时",
|
|
||||||
"hour_short": "戌",
|
|
||||||
"recommends": "嫁娶.移徙.安葬.进人口.求财",
|
|
||||||
"avoids": "出行.赴任.祈福.祭祀.开光.斋醮"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"hour": "亥时",
|
|
||||||
"hour_short": "亥",
|
|
||||||
"recommends": "嫁娶.移徙.交易.入宅.开市.安葬.求嗣.求财",
|
|
||||||
"avoids": "出行.赴任.动土.祈福.祭祀.修造.开光.斋醮"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"hour": "子时",
|
|
||||||
"hour_short": "子",
|
|
||||||
"recommends": "嫁娶.交易.入宅.开市.祈福.安葬.求嗣.求财",
|
|
||||||
"avoids": "出行.移徙.赴任.词讼.修造"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"hour": "丑时",
|
|
||||||
"hour_short": "丑",
|
|
||||||
"recommends": "嫁娶.祈福.安葬.祭祀.酬神.求财",
|
|
||||||
"avoids": "出行.赴任.动土.修造"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"hour": "寅时",
|
|
||||||
"hour_short": "寅",
|
|
||||||
"recommends": "嫁娶.出行.交易.开市.赴任.祈福.安床.祭祀.求嗣.求财",
|
|
||||||
"avoids": "盖屋.入殓.上梁"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"hour": "卯时",
|
|
||||||
"hour_short": "卯",
|
|
||||||
"recommends": "嫁娶.交易.入宅.开市.祈福.安床.安葬.求嗣.求财",
|
|
||||||
"avoids": "出行.赴任.修造"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"hour": "辰时",
|
|
||||||
"hour_short": "辰",
|
|
||||||
"recommends": "",
|
|
||||||
"avoids": "诸事不宜"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"hour": "巳时",
|
|
||||||
"hour_short": "巳",
|
|
||||||
"recommends": "嫁娶.出行.移徙.入宅.开市.祈福.安床.盖屋.祭祀.作灶",
|
|
||||||
"avoids": "安葬.修造.开光"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"hour": "午时",
|
|
||||||
"hour_short": "午",
|
|
||||||
"recommends": "嫁娶.出行.交易.开市.祈福.安床.求嗣.求财",
|
|
||||||
"avoids": "赴任.动土.词讼.修造"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"hour": "未时",
|
|
||||||
"hour_short": "未",
|
|
||||||
"recommends": "嫁娶.入宅.祈福.安葬.祭祀.修造.酬神.求财",
|
|
||||||
"avoids": "出行.赴任"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"hour": "申时",
|
|
||||||
"hour_short": "申",
|
|
||||||
"recommends": "嫁娶.出行.开市.赴任.安葬.求财",
|
|
||||||
"avoids": "祈福.祭祀.酬神.斋醮"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"julian_day": 2460919.5,
|
|
||||||
"nayin": {
|
|
||||||
"year": "覆灯火",
|
|
||||||
"month": "泉中水",
|
|
||||||
"day": "剑锋金",
|
|
||||||
"hour": "石榴木"
|
|
||||||
},
|
|
||||||
"baizi": {
|
|
||||||
"year_baizi": "性格温和,为人正直诚信。",
|
|
||||||
"day_baizi": "性格温和,为人正直诚信。"
|
|
||||||
},
|
|
||||||
"fortune": {
|
|
||||||
"today_luck": "今日学习运好,适合进修",
|
|
||||||
"career": "领导能力突出,升职有望",
|
|
||||||
"money": "偏财运不错,可小试投资",
|
|
||||||
"love": "感情需要沟通,避免误会"
|
|
||||||
},
|
|
||||||
"constants": {
|
|
||||||
"legal_holiday_list": [
|
|
||||||
{
|
|
||||||
"name": "元旦节",
|
|
||||||
"date": "2025-01-01",
|
|
||||||
"start": "2025-01-01",
|
|
||||||
"end": "2025-01-01"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "春节",
|
|
||||||
"date": "2025-01-29",
|
|
||||||
"start": "2025-01-26",
|
|
||||||
"end": "2025-02-08"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "清明节",
|
|
||||||
"date": "2025-04-04",
|
|
||||||
"start": "2025-04-04",
|
|
||||||
"end": "2025-04-06"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "劳动节",
|
|
||||||
"date": "2025-05-01",
|
|
||||||
"start": "2025-04-27",
|
|
||||||
"end": "2025-05-05"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "端午节",
|
|
||||||
"date": "2025-05-31",
|
|
||||||
"start": "2025-05-31",
|
|
||||||
"end": "2025-06-02"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "国庆中秋",
|
|
||||||
"date": "2025-10-01",
|
|
||||||
"start": "2025-09-28",
|
|
||||||
"end": "2025-10-11"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"phase_list": [
|
|
||||||
{
|
|
||||||
"name": "朔月",
|
|
||||||
"lunar_day": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "既朔月",
|
|
||||||
"lunar_day": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "蛾眉新月",
|
|
||||||
"lunar_day": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "蛾眉新月",
|
|
||||||
"lunar_day": 4
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "蛾眉月",
|
|
||||||
"lunar_day": 5
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "夕月",
|
|
||||||
"lunar_day": 6
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "上弦月",
|
|
||||||
"lunar_day": 7
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "上弦月",
|
|
||||||
"lunar_day": 8
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "九夜月",
|
|
||||||
"lunar_day": 9
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "宵月",
|
|
||||||
"lunar_day": 10
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "宵月",
|
|
||||||
"lunar_day": 11
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "宵月",
|
|
||||||
"lunar_day": 12
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "渐盈凸月",
|
|
||||||
"lunar_day": 13
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "小望月",
|
|
||||||
"lunar_day": 14
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "望月",
|
|
||||||
"lunar_day": 15
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "既望月",
|
|
||||||
"lunar_day": 16
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "立待月",
|
|
||||||
"lunar_day": 17
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "居待月",
|
|
||||||
"lunar_day": 18
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "寝待月",
|
|
||||||
"lunar_day": 19
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "更待月",
|
|
||||||
"lunar_day": 20
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "渐亏凸月",
|
|
||||||
"lunar_day": 21
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "下弦月",
|
|
||||||
"lunar_day": 22
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "下弦月",
|
|
||||||
"lunar_day": 23
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "有明月",
|
|
||||||
"lunar_day": 24
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "有明月",
|
|
||||||
"lunar_day": 25
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "蛾眉残月",
|
|
||||||
"lunar_day": 26
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "蛾眉残月",
|
|
||||||
"lunar_day": 27
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "残月",
|
|
||||||
"lunar_day": 28
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "晓月",
|
|
||||||
"lunar_day": 29
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "晦月",
|
|
||||||
"lunar_day": 30
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"zodiac_list": [
|
|
||||||
"鼠",
|
|
||||||
"牛",
|
|
||||||
"虎",
|
|
||||||
"兔",
|
|
||||||
"龙",
|
|
||||||
"蛇",
|
|
||||||
"马",
|
|
||||||
"羊",
|
|
||||||
"猴",
|
|
||||||
"鸡",
|
|
||||||
"狗",
|
|
||||||
"猪"
|
|
||||||
],
|
|
||||||
"constellation_list": [
|
|
||||||
{
|
|
||||||
"name": "白羊",
|
|
||||||
"desc": "白羊座",
|
|
||||||
"start": "3月21日",
|
|
||||||
"end": "4月19日",
|
|
||||||
"range": "3月21日~4月19日",
|
|
||||||
"start_month": 3,
|
|
||||||
"start_day": 21,
|
|
||||||
"end_month": 4,
|
|
||||||
"end_day": 19
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "金牛",
|
|
||||||
"desc": "金牛座",
|
|
||||||
"start": "4月20日",
|
|
||||||
"end": "5月20日",
|
|
||||||
"range": "4月20日~5月20日",
|
|
||||||
"start_month": 4,
|
|
||||||
"start_day": 20,
|
|
||||||
"end_month": 5,
|
|
||||||
"end_day": 20
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "双子",
|
|
||||||
"desc": "双子座",
|
|
||||||
"start": "5月21日",
|
|
||||||
"end": "6月21日",
|
|
||||||
"range": "5月21日~6月21日",
|
|
||||||
"start_month": 5,
|
|
||||||
"start_day": 21,
|
|
||||||
"end_month": 6,
|
|
||||||
"end_day": 21
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "巨蟹",
|
|
||||||
"desc": "巨蟹座",
|
|
||||||
"start": "6月22日",
|
|
||||||
"end": "7月22日",
|
|
||||||
"range": "6月22日~7月22日",
|
|
||||||
"start_month": 6,
|
|
||||||
"start_day": 22,
|
|
||||||
"end_month": 7,
|
|
||||||
"end_day": 22
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "狮子",
|
|
||||||
"desc": "狮子座",
|
|
||||||
"start": "7月23日",
|
|
||||||
"end": "8月22日",
|
|
||||||
"range": "7月23日~8月22日",
|
|
||||||
"start_month": 7,
|
|
||||||
"start_day": 23,
|
|
||||||
"end_month": 8,
|
|
||||||
"end_day": 22
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "处女",
|
|
||||||
"desc": "处女座",
|
|
||||||
"start": "8月23日",
|
|
||||||
"end": "9月22日",
|
|
||||||
"range": "8月23日~9月22日",
|
|
||||||
"start_month": 8,
|
|
||||||
"start_day": 23,
|
|
||||||
"end_month": 9,
|
|
||||||
"end_day": 22
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "天秤",
|
|
||||||
"desc": "天秤座",
|
|
||||||
"start": "9月23日",
|
|
||||||
"end": "10月23日",
|
|
||||||
"range": "9月23日~10月23日",
|
|
||||||
"start_month": 9,
|
|
||||||
"start_day": 23,
|
|
||||||
"end_month": 10,
|
|
||||||
"end_day": 23
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "天蝎",
|
|
||||||
"desc": "天蝎座",
|
|
||||||
"start": "10月24日",
|
|
||||||
"end": "11月22日",
|
|
||||||
"range": "10月24日~11月22日",
|
|
||||||
"start_month": 10,
|
|
||||||
"start_day": 24,
|
|
||||||
"end_month": 11,
|
|
||||||
"end_day": 22
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "射手",
|
|
||||||
"desc": "射手座",
|
|
||||||
"start": "11月23日",
|
|
||||||
"end": "12月21日",
|
|
||||||
"range": "11月23日~12月21日",
|
|
||||||
"start_month": 11,
|
|
||||||
"start_day": 23,
|
|
||||||
"end_month": 12,
|
|
||||||
"end_day": 21
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "摩羯",
|
|
||||||
"desc": "摩羯座",
|
|
||||||
"start": "12月22日",
|
|
||||||
"end": "1月19日",
|
|
||||||
"range": "12月22日~1月19日",
|
|
||||||
"start_month": 12,
|
|
||||||
"start_day": 22,
|
|
||||||
"end_month": 1,
|
|
||||||
"end_day": 19
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "水瓶",
|
|
||||||
"desc": "水瓶座",
|
|
||||||
"start": "1月20日",
|
|
||||||
"end": "2月18日",
|
|
||||||
"range": "1月20日~2月18日",
|
|
||||||
"start_month": 1,
|
|
||||||
"start_day": 20,
|
|
||||||
"end_month": 2,
|
|
||||||
"end_day": 18
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "双鱼",
|
|
||||||
"desc": "双鱼座",
|
|
||||||
"start": "2月19日",
|
|
||||||
"end": "3月20日",
|
|
||||||
"range": "2月19日~3月20日",
|
|
||||||
"start_month": 2,
|
|
||||||
"start_day": 19,
|
|
||||||
"end_month": 3,
|
|
||||||
"end_day": 20
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"heaven_stems": [
|
|
||||||
"甲",
|
|
||||||
"乙",
|
|
||||||
"丙",
|
|
||||||
"丁",
|
|
||||||
"戊",
|
|
||||||
"己",
|
|
||||||
"庚",
|
|
||||||
"辛",
|
|
||||||
"壬",
|
|
||||||
"癸"
|
|
||||||
],
|
|
||||||
"earth_branches": [
|
|
||||||
"子",
|
|
||||||
"丑",
|
|
||||||
"寅",
|
|
||||||
"卯",
|
|
||||||
"辰",
|
|
||||||
"巳",
|
|
||||||
"午",
|
|
||||||
"未",
|
|
||||||
"申",
|
|
||||||
"酉",
|
|
||||||
"戌",
|
|
||||||
"亥"
|
|
||||||
],
|
|
||||||
"solar_terms": [
|
|
||||||
{
|
|
||||||
"name": "立春",
|
|
||||||
"desc": "春季开始"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "雨水",
|
|
||||||
"desc": "降雨增多"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "惊蛰",
|
|
||||||
"desc": "春雷乍响"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "春分",
|
|
||||||
"desc": "昼夜等长"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "清明",
|
|
||||||
"desc": "天清地明"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "谷雨",
|
|
||||||
"desc": "雨生百谷"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "立夏",
|
|
||||||
"desc": "夏季开始"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "小满",
|
|
||||||
"desc": "麦粒渐满"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "芒种",
|
|
||||||
"desc": "麦类收割"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "夏至",
|
|
||||||
"desc": "白昼最长"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "小暑",
|
|
||||||
"desc": "天气渐热"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "大暑",
|
|
||||||
"desc": "一年最热"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "立秋",
|
|
||||||
"desc": "秋季开始"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "处暑",
|
|
||||||
"desc": "暑热结束"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "白露",
|
|
||||||
"desc": "露水增多"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "秋分",
|
|
||||||
"desc": "昼夜等长"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "寒露",
|
|
||||||
"desc": "露水渐凉"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "霜降",
|
|
||||||
"desc": "开始降霜"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "立冬",
|
|
||||||
"desc": "冬季开始"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "小雪",
|
|
||||||
"desc": "开始降雪"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "大雪",
|
|
||||||
"desc": "降雪增多"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "冬至",
|
|
||||||
"desc": "白昼最短"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "小寒",
|
|
||||||
"desc": "天气渐冷"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "大寒",
|
|
||||||
"desc": "一年最冷"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,145 +0,0 @@
|
|||||||
/* 背景样式文件 */
|
|
||||||
body {
|
|
||||||
background: linear-gradient(135deg, #e8f5e8 0%, #d4f1d4 25%, #c8ecc8 50%, #b8e6b8 75%, #a8d5ba 100%);
|
|
||||||
background-attachment: fixed;
|
|
||||||
background-size: 400% 400%;
|
|
||||||
animation: gradientShift 15s ease infinite;
|
|
||||||
position: relative;
|
|
||||||
overflow-x: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 背景渐变动画 */
|
|
||||||
@keyframes gradientShift {
|
|
||||||
0% {
|
|
||||||
background-position: 0% 50%;
|
|
||||||
}
|
|
||||||
50% {
|
|
||||||
background-position: 100% 50%;
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
background-position: 0% 50%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 背景装饰元素 */
|
|
||||||
body::before {
|
|
||||||
content: '';
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background-image:
|
|
||||||
radial-gradient(circle at 20% 80%, rgba(168, 213, 186, 0.1) 0%, transparent 50%),
|
|
||||||
radial-gradient(circle at 80% 20%, rgba(107, 183, 123, 0.1) 0%, transparent 50%),
|
|
||||||
radial-gradient(circle at 40% 40%, rgba(200, 236, 200, 0.1) 0%, transparent 50%);
|
|
||||||
pointer-events: none;
|
|
||||||
z-index: -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 浮动装饰圆点 */
|
|
||||||
body::after {
|
|
||||||
content: '';
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background-image:
|
|
||||||
radial-gradient(2px 2px at 20px 30px, rgba(168, 213, 186, 0.3), transparent),
|
|
||||||
radial-gradient(2px 2px at 40px 70px, rgba(107, 183, 123, 0.2), transparent),
|
|
||||||
radial-gradient(1px 1px at 90px 40px, rgba(200, 236, 200, 0.4), transparent),
|
|
||||||
radial-gradient(1px 1px at 130px 80px, rgba(168, 213, 186, 0.2), transparent),
|
|
||||||
radial-gradient(2px 2px at 160px 30px, rgba(107, 183, 123, 0.3), transparent);
|
|
||||||
background-repeat: repeat;
|
|
||||||
background-size: 200px 100px;
|
|
||||||
animation: float 20s linear infinite;
|
|
||||||
pointer-events: none;
|
|
||||||
z-index: -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes float {
|
|
||||||
0% {
|
|
||||||
transform: translateY(0px);
|
|
||||||
}
|
|
||||||
50% {
|
|
||||||
transform: translateY(-10px);
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
transform: translateY(0px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 云朵装饰效果 */
|
|
||||||
.container::before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
top: -50px;
|
|
||||||
right: -50px;
|
|
||||||
width: 200px;
|
|
||||||
height: 100px;
|
|
||||||
background: rgba(255, 255, 255, 0.1);
|
|
||||||
border-radius: 50px;
|
|
||||||
box-shadow:
|
|
||||||
-30px 20px 0 rgba(255, 255, 255, 0.08),
|
|
||||||
30px 40px 0 rgba(255, 255, 255, 0.06);
|
|
||||||
animation: cloudFloat 25s ease-in-out infinite;
|
|
||||||
pointer-events: none;
|
|
||||||
z-index: -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container::after {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
bottom: -30px;
|
|
||||||
left: -30px;
|
|
||||||
width: 150px;
|
|
||||||
height: 80px;
|
|
||||||
background: rgba(255, 255, 255, 0.08);
|
|
||||||
border-radius: 40px;
|
|
||||||
box-shadow:
|
|
||||||
20px 15px 0 rgba(255, 255, 255, 0.06),
|
|
||||||
-20px 25px 0 rgba(255, 255, 255, 0.04);
|
|
||||||
animation: cloudFloat 30s ease-in-out infinite reverse;
|
|
||||||
pointer-events: none;
|
|
||||||
z-index: -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes cloudFloat {
|
|
||||||
0%, 100% {
|
|
||||||
transform: translateX(0px) translateY(0px);
|
|
||||||
}
|
|
||||||
25% {
|
|
||||||
transform: translateX(20px) translateY(-10px);
|
|
||||||
}
|
|
||||||
50% {
|
|
||||||
transform: translateX(-10px) translateY(-20px);
|
|
||||||
}
|
|
||||||
75% {
|
|
||||||
transform: translateX(15px) translateY(-5px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 响应式背景调整 */
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
body::after {
|
|
||||||
background-size: 150px 75px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container::before,
|
|
||||||
.container::after {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 480px) {
|
|
||||||
body {
|
|
||||||
background: linear-gradient(135deg, #e8f5e8 0%, #d4f1d4 50%, #a8d5ba 100%);
|
|
||||||
animation: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
body::before,
|
|
||||||
body::after {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,409 +0,0 @@
|
|||||||
/* 基础样式重置 */
|
|
||||||
* {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
font-family: 'Microsoft YaHei', Arial, sans-serif;
|
|
||||||
line-height: 1.6;
|
|
||||||
color: #333;
|
|
||||||
min-height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
max-width: 1200px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 头部样式 */
|
|
||||||
.header {
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header h1 {
|
|
||||||
color: #2d5a3d;
|
|
||||||
font-size: 2.5rem;
|
|
||||||
font-weight: 300;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 搜索区域 */
|
|
||||||
.search-section {
|
|
||||||
margin-bottom: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-box {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 10px;
|
|
||||||
max-width: 500px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
#cityInput {
|
|
||||||
flex: 1;
|
|
||||||
padding: 12px 16px;
|
|
||||||
border: 2px solid #a8d5ba;
|
|
||||||
border-radius: 25px;
|
|
||||||
font-size: 16px;
|
|
||||||
outline: none;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
background: rgba(255, 255, 255, 0.9);
|
|
||||||
}
|
|
||||||
|
|
||||||
#cityInput:focus {
|
|
||||||
border-color: #6bb77b;
|
|
||||||
box-shadow: 0 0 10px rgba(107, 183, 123, 0.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
#searchBtn {
|
|
||||||
padding: 12px 24px;
|
|
||||||
background: linear-gradient(135deg, #6bb77b, #5a9f6a);
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
border-radius: 25px;
|
|
||||||
font-size: 16px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
#searchBtn:hover {
|
|
||||||
background: linear-gradient(135deg, #5a9f6a, #4a8759);
|
|
||||||
transform: translateY(-2px);
|
|
||||||
box-shadow: 0 4px 12px rgba(107, 183, 123, 0.4);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 加载动画 */
|
|
||||||
.loading {
|
|
||||||
text-align: center;
|
|
||||||
padding: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.spinner {
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
border: 4px solid #e8f5e8;
|
|
||||||
border-top: 4px solid #6bb77b;
|
|
||||||
border-radius: 50%;
|
|
||||||
animation: spin 1s linear infinite;
|
|
||||||
margin: 0 auto 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes spin {
|
|
||||||
0% { transform: rotate(0deg); }
|
|
||||||
100% { transform: rotate(360deg); }
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 天气容器 */
|
|
||||||
.weather-container {
|
|
||||||
background: rgba(255, 255, 255, 0.95);
|
|
||||||
border-radius: 20px;
|
|
||||||
padding: 30px;
|
|
||||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
|
|
||||||
backdrop-filter: blur(10px);
|
|
||||||
border: 1px solid rgba(168, 213, 186, 0.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 位置信息 */
|
|
||||||
.location-info {
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 30px;
|
|
||||||
padding-bottom: 20px;
|
|
||||||
border-bottom: 2px solid #e8f5e8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.location-info h2 {
|
|
||||||
color: #2d5a3d;
|
|
||||||
font-size: 2rem;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.location-info p {
|
|
||||||
color: #666;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 当前天气 */
|
|
||||||
.current-weather {
|
|
||||||
margin-bottom: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.weather-main {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 25px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.temperature {
|
|
||||||
font-size: 4rem;
|
|
||||||
font-weight: 300;
|
|
||||||
color: #2d5a3d;
|
|
||||||
}
|
|
||||||
|
|
||||||
.unit {
|
|
||||||
font-size: 2rem;
|
|
||||||
color: #6bb77b;
|
|
||||||
}
|
|
||||||
|
|
||||||
.weather-desc p:first-child {
|
|
||||||
font-size: 1.5rem;
|
|
||||||
color: #2d5a3d;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.weather-desc p:last-child {
|
|
||||||
color: #666;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 天气详情 */
|
|
||||||
.weather-details {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
|
||||||
gap: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.detail-item {
|
|
||||||
background: rgba(168, 213, 186, 0.1);
|
|
||||||
padding: 15px;
|
|
||||||
border-radius: 12px;
|
|
||||||
text-align: center;
|
|
||||||
border: 1px solid rgba(168, 213, 186, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.detail-item .label {
|
|
||||||
display: block;
|
|
||||||
color: #666;
|
|
||||||
font-size: 12px;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.detail-item span:last-child {
|
|
||||||
color: #2d5a3d;
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 生活指数 */
|
|
||||||
.life-index {
|
|
||||||
margin-top: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.life-index h3 {
|
|
||||||
color: #2d5a3d;
|
|
||||||
font-size: 1.5rem;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.index-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
|
||||||
gap: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.index-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
background: rgba(168, 213, 186, 0.05);
|
|
||||||
padding: 20px;
|
|
||||||
border-radius: 15px;
|
|
||||||
border: 1px solid rgba(168, 213, 186, 0.2);
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.index-item:hover {
|
|
||||||
background: rgba(168, 213, 186, 0.1);
|
|
||||||
transform: translateY(-2px);
|
|
||||||
box-shadow: 0 4px 12px rgba(168, 213, 186, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.index-icon {
|
|
||||||
font-size: 2rem;
|
|
||||||
margin-right: 15px;
|
|
||||||
width: 50px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.index-content h4 {
|
|
||||||
color: #2d5a3d;
|
|
||||||
font-size: 16px;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.index-content p {
|
|
||||||
color: #6bb77b;
|
|
||||||
font-weight: 600;
|
|
||||||
margin-bottom: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.index-content span {
|
|
||||||
color: #666;
|
|
||||||
font-size: 13px;
|
|
||||||
line-height: 1.4;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 更新时间 */
|
|
||||||
.update-time {
|
|
||||||
text-align: center;
|
|
||||||
margin-top: 30px;
|
|
||||||
padding-top: 20px;
|
|
||||||
border-top: 1px solid #e8f5e8;
|
|
||||||
color: #666;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 错误信息 */
|
|
||||||
.error-message {
|
|
||||||
text-align: center;
|
|
||||||
padding: 40px;
|
|
||||||
background: rgba(255, 107, 107, 0.1);
|
|
||||||
border-radius: 15px;
|
|
||||||
border: 1px solid rgba(255, 107, 107, 0.2);
|
|
||||||
color: #d63031;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 平板端适配 (768px - 1024px) */
|
|
||||||
@media (min-width: 768px) and (max-width: 1024px) {
|
|
||||||
.container {
|
|
||||||
padding: 25px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header h1 {
|
|
||||||
font-size: 2.2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.temperature {
|
|
||||||
font-size: 3.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.weather-details {
|
|
||||||
grid-template-columns: repeat(3, 1fr);
|
|
||||||
}
|
|
||||||
|
|
||||||
.index-grid {
|
|
||||||
grid-template-columns: repeat(2, 1fr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 电脑端适配 (1024px+) */
|
|
||||||
@media (min-width: 1024px) {
|
|
||||||
.container {
|
|
||||||
padding: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.weather-container {
|
|
||||||
padding: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.weather-main {
|
|
||||||
justify-content: space-around;
|
|
||||||
}
|
|
||||||
|
|
||||||
.weather-details {
|
|
||||||
grid-template-columns: repeat(6, 1fr);
|
|
||||||
}
|
|
||||||
|
|
||||||
.index-grid {
|
|
||||||
grid-template-columns: repeat(3, 1fr);
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-box {
|
|
||||||
max-width: 600px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 手机端适配 (768px以下) */
|
|
||||||
@media (max-width: 767px) {
|
|
||||||
.container {
|
|
||||||
padding: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header h1 {
|
|
||||||
font-size: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-box {
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#searchBtn {
|
|
||||||
padding: 14px 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.weather-container {
|
|
||||||
padding: 20px;
|
|
||||||
margin: 0 -5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.weather-main {
|
|
||||||
flex-direction: column;
|
|
||||||
text-align: center;
|
|
||||||
gap: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.temperature {
|
|
||||||
font-size: 3rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.weather-details {
|
|
||||||
grid-template-columns: repeat(2, 1fr);
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.detail-item {
|
|
||||||
padding: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.index-grid {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
gap: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.index-item {
|
|
||||||
padding: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.index-icon {
|
|
||||||
font-size: 1.5rem;
|
|
||||||
width: 40px;
|
|
||||||
margin-right: 12px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 超小屏幕适配 (480px以下) */
|
|
||||||
@media (max-width: 480px) {
|
|
||||||
.container {
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header h1 {
|
|
||||||
font-size: 1.8rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.weather-container {
|
|
||||||
padding: 15px;
|
|
||||||
border-radius: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.temperature {
|
|
||||||
font-size: 2.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.weather-details {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
|
|
||||||
.detail-item {
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,140 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="zh-CN">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>实时天气查询</title>
|
|
||||||
<link rel="stylesheet" href="css/style.css">
|
|
||||||
<link rel="stylesheet" href="css/background.css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="container">
|
|
||||||
<header class="header">
|
|
||||||
<h1>实时天气查询</h1>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<div class="search-section">
|
|
||||||
<div class="search-box">
|
|
||||||
<input type="text" id="cityInput" placeholder="请输入城市名称(如:北京)" value="北京">
|
|
||||||
<button id="searchBtn">查询天气</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="loading" id="loading" style="display: none;">
|
|
||||||
<div class="spinner"></div>
|
|
||||||
<p>正在获取天气信息...</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="weather-container" id="weatherContainer" style="display: none;">
|
|
||||||
<div class="location-info">
|
|
||||||
<h2 id="locationName"></h2>
|
|
||||||
<p id="locationDetail"></p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="current-weather">
|
|
||||||
<div class="weather-main">
|
|
||||||
<div class="temperature">
|
|
||||||
<span id="temperature"></span>
|
|
||||||
<span class="unit">°C</span>
|
|
||||||
</div>
|
|
||||||
<div class="weather-desc">
|
|
||||||
<p id="weatherCondition"></p>
|
|
||||||
<p id="feelsLike"></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="weather-details">
|
|
||||||
<div class="detail-item">
|
|
||||||
<span class="label">湿度</span>
|
|
||||||
<span id="humidity"></span>
|
|
||||||
</div>
|
|
||||||
<div class="detail-item">
|
|
||||||
<span class="label">风向</span>
|
|
||||||
<span id="windDirection"></span>
|
|
||||||
</div>
|
|
||||||
<div class="detail-item">
|
|
||||||
<span class="label">风力</span>
|
|
||||||
<span id="windStrength"></span>
|
|
||||||
</div>
|
|
||||||
<div class="detail-item">
|
|
||||||
<span class="label">气压</span>
|
|
||||||
<span id="pressure"></span>
|
|
||||||
</div>
|
|
||||||
<div class="detail-item">
|
|
||||||
<span class="label">能见度</span>
|
|
||||||
<span id="visibility"></span>
|
|
||||||
</div>
|
|
||||||
<div class="detail-item">
|
|
||||||
<span class="label">空气质量</span>
|
|
||||||
<span id="aqi"></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="life-index">
|
|
||||||
<h3>生活指数</h3>
|
|
||||||
<div class="index-grid">
|
|
||||||
<div class="index-item">
|
|
||||||
<div class="index-icon comfort">🌡️</div>
|
|
||||||
<div class="index-content">
|
|
||||||
<h4>舒适度</h4>
|
|
||||||
<p id="comfortLevel"></p>
|
|
||||||
<span id="comfortDesc"></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="index-item">
|
|
||||||
<div class="index-icon clothing">👕</div>
|
|
||||||
<div class="index-content">
|
|
||||||
<h4>穿衣指数</h4>
|
|
||||||
<p id="clothingLevel"></p>
|
|
||||||
<span id="clothingDesc"></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="index-item">
|
|
||||||
<div class="index-icon umbrella">☂️</div>
|
|
||||||
<div class="index-content">
|
|
||||||
<h4>雨伞指数</h4>
|
|
||||||
<p id="umbrellaLevel"></p>
|
|
||||||
<span id="umbrellaDesc"></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="index-item">
|
|
||||||
<div class="index-icon uv">☀️</div>
|
|
||||||
<div class="index-content">
|
|
||||||
<h4>紫外线</h4>
|
|
||||||
<p id="uvLevel"></p>
|
|
||||||
<span id="uvDesc"></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="index-item">
|
|
||||||
<div class="index-icon travel">🚗</div>
|
|
||||||
<div class="index-content">
|
|
||||||
<h4>出行指数</h4>
|
|
||||||
<p id="travelLevel"></p>
|
|
||||||
<span id="travelDesc"></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="index-item">
|
|
||||||
<div class="index-icon sport">🏃</div>
|
|
||||||
<div class="index-content">
|
|
||||||
<h4>运动指数</h4>
|
|
||||||
<p id="sportLevel"></p>
|
|
||||||
<span id="sportDesc"></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="update-time">
|
|
||||||
<p>更新时间:<span id="updateTime"></span></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="error-message" id="errorMessage" style="display: none;">
|
|
||||||
<p>获取天气信息失败,请稍后重试</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src="js/script.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,252 +0,0 @@
|
|||||||
// 天气查询应用
|
|
||||||
class WeatherApp {
|
|
||||||
constructor() {
|
|
||||||
this.apiEndpoints = [
|
|
||||||
'https://60s-cf.viki.moe',
|
|
||||||
'https://60s.viki.moe',
|
|
||||||
'https://60s.b23.run',
|
|
||||||
'https://60s.114128.xyz',
|
|
||||||
'https://60s-cf.114128.xyz'
|
|
||||||
];
|
|
||||||
this.currentEndpointIndex = 0;
|
|
||||||
this.init();
|
|
||||||
}
|
|
||||||
|
|
||||||
init() {
|
|
||||||
this.bindEvents();
|
|
||||||
// 页面加载时自动查询北京天气
|
|
||||||
this.searchWeather('北京');
|
|
||||||
}
|
|
||||||
|
|
||||||
bindEvents() {
|
|
||||||
const searchBtn = document.getElementById('searchBtn');
|
|
||||||
const cityInput = document.getElementById('cityInput');
|
|
||||||
|
|
||||||
searchBtn.addEventListener('click', () => {
|
|
||||||
const city = cityInput.value.trim();
|
|
||||||
if (city) {
|
|
||||||
this.searchWeather(city);
|
|
||||||
} else {
|
|
||||||
this.showError('请输入城市名称');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
cityInput.addEventListener('keypress', (e) => {
|
|
||||||
if (e.key === 'Enter') {
|
|
||||||
const city = cityInput.value.trim();
|
|
||||||
if (city) {
|
|
||||||
this.searchWeather(city);
|
|
||||||
} else {
|
|
||||||
this.showError('请输入城市名称');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 防止输入框为空时查询
|
|
||||||
cityInput.addEventListener('input', () => {
|
|
||||||
const searchBtn = document.getElementById('searchBtn');
|
|
||||||
searchBtn.disabled = !cityInput.value.trim();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async searchWeather(city) {
|
|
||||||
this.showLoading();
|
|
||||||
|
|
||||||
for (let i = 0; i < this.apiEndpoints.length; i++) {
|
|
||||||
try {
|
|
||||||
const endpoint = this.apiEndpoints[this.currentEndpointIndex];
|
|
||||||
const response = await this.fetchWeatherData(endpoint, city);
|
|
||||||
|
|
||||||
if (response && response.code === 200) {
|
|
||||||
this.displayWeatherData(response.data);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.warn(`API ${this.apiEndpoints[this.currentEndpointIndex]} 请求失败:`, error);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 切换到下一个API端点
|
|
||||||
this.currentEndpointIndex = (this.currentEndpointIndex + 1) % this.apiEndpoints.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 所有API都失败了
|
|
||||||
this.showError('获取天气信息失败,请检查网络连接或稍后重试');
|
|
||||||
}
|
|
||||||
|
|
||||||
async fetchWeatherData(endpoint, city) {
|
|
||||||
const url = `${endpoint}/v2/weather?query=${encodeURIComponent(city)}`;
|
|
||||||
|
|
||||||
const response = await fetch(url, {
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
'Accept': 'application/json',
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
timeout: 10000
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`HTTP ${response.status}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return await response.json();
|
|
||||||
}
|
|
||||||
|
|
||||||
displayWeatherData(data) {
|
|
||||||
const { location, realtime } = data;
|
|
||||||
|
|
||||||
// 显示位置信息
|
|
||||||
document.getElementById('locationName').textContent = location.formatted;
|
|
||||||
document.getElementById('locationDetail').textContent =
|
|
||||||
`${location.province} ${location.city} | 邮编: ${location.zip_code}`;
|
|
||||||
|
|
||||||
// 显示当前天气
|
|
||||||
document.getElementById('temperature').textContent = realtime.temperature;
|
|
||||||
document.getElementById('weatherCondition').textContent = realtime.weather;
|
|
||||||
|
|
||||||
// 体感温度转换(API返回的是华氏度,需要转换为摄氏度)
|
|
||||||
const feelsLikeCelsius = this.fahrenheitToCelsius(realtime.temperature_feels_like);
|
|
||||||
document.getElementById('feelsLike').textContent =
|
|
||||||
`体感温度 ${feelsLikeCelsius}°C`;
|
|
||||||
|
|
||||||
// 显示天气详情
|
|
||||||
document.getElementById('humidity').textContent = `${realtime.humidity}%`;
|
|
||||||
document.getElementById('windDirection').textContent = realtime.wind_direction;
|
|
||||||
document.getElementById('windStrength').textContent = realtime.wind_strength;
|
|
||||||
document.getElementById('pressure').textContent = `${realtime.pressure} hPa`;
|
|
||||||
document.getElementById('visibility').textContent = realtime.visibility;
|
|
||||||
|
|
||||||
// 空气质量显示
|
|
||||||
const aqiElement = document.getElementById('aqi');
|
|
||||||
aqiElement.textContent = `${realtime.aqi} (PM2.5: ${realtime.pm25})`;
|
|
||||||
aqiElement.className = this.getAQIClass(realtime.aqi);
|
|
||||||
|
|
||||||
// 显示生活指数
|
|
||||||
const lifeIndex = realtime.life_index;
|
|
||||||
this.displayLifeIndex('comfort', lifeIndex.comfort);
|
|
||||||
this.displayLifeIndex('clothing', lifeIndex.clothing);
|
|
||||||
this.displayLifeIndex('umbrella', lifeIndex.umbrella);
|
|
||||||
this.displayLifeIndex('uv', lifeIndex.uv);
|
|
||||||
this.displayLifeIndex('travel', lifeIndex.travel);
|
|
||||||
this.displayLifeIndex('sport', lifeIndex.sport);
|
|
||||||
|
|
||||||
// 显示更新时间
|
|
||||||
document.getElementById('updateTime').textContent =
|
|
||||||
`${realtime.updated} (${realtime.updated_at})`;
|
|
||||||
|
|
||||||
this.showWeatherContainer();
|
|
||||||
}
|
|
||||||
|
|
||||||
displayLifeIndex(type, indexData) {
|
|
||||||
const levelElement = document.getElementById(`${type}Level`);
|
|
||||||
const descElement = document.getElementById(`${type}Desc`);
|
|
||||||
|
|
||||||
if (levelElement && descElement && indexData) {
|
|
||||||
levelElement.textContent = indexData.level;
|
|
||||||
descElement.textContent = indexData.desc;
|
|
||||||
|
|
||||||
// 根据指数级别设置颜色
|
|
||||||
levelElement.className = this.getIndexLevelClass(indexData.level);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getAQIClass(aqi) {
|
|
||||||
if (aqi <= 50) return 'aqi-good';
|
|
||||||
if (aqi <= 100) return 'aqi-moderate';
|
|
||||||
if (aqi <= 150) return 'aqi-unhealthy-sensitive';
|
|
||||||
if (aqi <= 200) return 'aqi-unhealthy';
|
|
||||||
if (aqi <= 300) return 'aqi-very-unhealthy';
|
|
||||||
return 'aqi-hazardous';
|
|
||||||
}
|
|
||||||
|
|
||||||
getIndexLevelClass(level) {
|
|
||||||
const levelMap = {
|
|
||||||
'优': 'level-excellent',
|
|
||||||
'良': 'level-good',
|
|
||||||
'适宜': 'level-suitable',
|
|
||||||
'舒适': 'level-comfortable',
|
|
||||||
'较适宜': 'level-fairly-suitable',
|
|
||||||
'不宜': 'level-unsuitable',
|
|
||||||
'较不宜': 'level-fairly-unsuitable',
|
|
||||||
'带伞': 'level-bring-umbrella',
|
|
||||||
'最弱': 'level-weakest',
|
|
||||||
'弱': 'level-weak',
|
|
||||||
'中等': 'level-moderate',
|
|
||||||
'强': 'level-strong',
|
|
||||||
'很强': 'level-very-strong'
|
|
||||||
};
|
|
||||||
|
|
||||||
return levelMap[level] || 'level-default';
|
|
||||||
}
|
|
||||||
|
|
||||||
// 华氏度转摄氏度
|
|
||||||
fahrenheitToCelsius(fahrenheit) {
|
|
||||||
const celsius = (fahrenheit - 32) * 5 / 9;
|
|
||||||
return Math.round(celsius * 10) / 10; // 保留一位小数
|
|
||||||
}
|
|
||||||
|
|
||||||
showLoading() {
|
|
||||||
document.getElementById('loading').style.display = 'block';
|
|
||||||
document.getElementById('weatherContainer').style.display = 'none';
|
|
||||||
document.getElementById('errorMessage').style.display = 'none';
|
|
||||||
}
|
|
||||||
|
|
||||||
showWeatherContainer() {
|
|
||||||
document.getElementById('loading').style.display = 'none';
|
|
||||||
document.getElementById('weatherContainer').style.display = 'block';
|
|
||||||
document.getElementById('errorMessage').style.display = 'none';
|
|
||||||
}
|
|
||||||
|
|
||||||
showError(message) {
|
|
||||||
document.getElementById('loading').style.display = 'none';
|
|
||||||
document.getElementById('weatherContainer').style.display = 'none';
|
|
||||||
const errorElement = document.getElementById('errorMessage');
|
|
||||||
errorElement.style.display = 'block';
|
|
||||||
errorElement.querySelector('p').textContent = message;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加生活指数级别样式
|
|
||||||
const style = document.createElement('style');
|
|
||||||
style.textContent = `
|
|
||||||
.aqi-good { color: #52c41a; }
|
|
||||||
.aqi-moderate { color: #faad14; }
|
|
||||||
.aqi-unhealthy-sensitive { color: #fa8c16; }
|
|
||||||
.aqi-unhealthy { color: #f5222d; }
|
|
||||||
.aqi-very-unhealthy { color: #a0206e; }
|
|
||||||
.aqi-hazardous { color: #722ed1; }
|
|
||||||
|
|
||||||
.level-excellent, .level-suitable, .level-comfortable { color: #52c41a; }
|
|
||||||
.level-good, .level-fairly-suitable { color: #1890ff; }
|
|
||||||
.level-bring-umbrella, .level-moderate { color: #faad14; }
|
|
||||||
.level-unsuitable, .level-fairly-unsuitable { color: #f5222d; }
|
|
||||||
.level-weakest, .level-weak { color: #52c41a; }
|
|
||||||
.level-strong, .level-very-strong { color: #fa8c16; }
|
|
||||||
.level-default { color: #666; }
|
|
||||||
`;
|
|
||||||
document.head.appendChild(style);
|
|
||||||
|
|
||||||
// 页面加载完成后初始化应用
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
|
||||||
new WeatherApp();
|
|
||||||
});
|
|
||||||
|
|
||||||
// 添加页面可见性检测,当页面重新可见时刷新数据
|
|
||||||
document.addEventListener('visibilitychange', () => {
|
|
||||||
if (!document.hidden) {
|
|
||||||
const cityInput = document.getElementById('cityInput');
|
|
||||||
const city = cityInput.value.trim() || '北京';
|
|
||||||
// 延迟1秒刷新,避免频繁请求
|
|
||||||
setTimeout(() => {
|
|
||||||
if (window.weatherApp) {
|
|
||||||
window.weatherApp.searchWeather(city);
|
|
||||||
}
|
|
||||||
}, 1000);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 将应用实例暴露到全局,方便调试和其他功能调用
|
|
||||||
window.weatherApp = null;
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
|
||||||
window.weatherApp = new WeatherApp();
|
|
||||||
});
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
[
|
|
||||||
"https://60s-cf.viki.moe",
|
|
||||||
"https://60s.viki.moe",
|
|
||||||
"https://60s.b23.run",
|
|
||||||
"https://60s.114128.xyz",
|
|
||||||
"https://60s-cf.114128.xyz"
|
|
||||||
]
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
{
|
|
||||||
"code": 200,
|
|
||||||
"message": "获取成功。数据来自官方/权威源头,以确保稳定与实时。开源地址 https://github.com/vikiboss/60s,反馈群 595941841",
|
|
||||||
"data": {
|
|
||||||
"location": {
|
|
||||||
"province": "北京",
|
|
||||||
"city": "北京",
|
|
||||||
"town": "北京",
|
|
||||||
"formatted": "北京",
|
|
||||||
"location_id": "101010100",
|
|
||||||
"detail_url": "http://www.weather.com.cn/weather/101010100.shtml",
|
|
||||||
"is_province": true,
|
|
||||||
"is_city": false,
|
|
||||||
"is_town": false,
|
|
||||||
"area_code": "10",
|
|
||||||
"zip_code": "100000"
|
|
||||||
},
|
|
||||||
"realtime": {
|
|
||||||
"weather": "小雨转多云",
|
|
||||||
"weather_desc": "未知",
|
|
||||||
"weather_code": "d7",
|
|
||||||
"temperature": 26,
|
|
||||||
"temperature_feels_like": 81.1,
|
|
||||||
"humidity": 78,
|
|
||||||
"wind_direction": "南风转北风",
|
|
||||||
"wind_strength": "\u003C3级",
|
|
||||||
"wind_speed": "5km/h",
|
|
||||||
"pressure": 1008,
|
|
||||||
"visibility": "8km",
|
|
||||||
"aqi": 37,
|
|
||||||
"pm25": 37,
|
|
||||||
"rainfall": 0,
|
|
||||||
"rainfall_24h": 0,
|
|
||||||
"updated": "2025-08-29 08:00:00",
|
|
||||||
"updated_at": "15:10",
|
|
||||||
"life_index": {
|
|
||||||
"comfort": {
|
|
||||||
"level": "舒适",
|
|
||||||
"desc": "白天温度宜人,风力不大。"
|
|
||||||
},
|
|
||||||
"clothing": {
|
|
||||||
"level": "舒适",
|
|
||||||
"desc": "建议穿长袖衬衫单裤等服装。"
|
|
||||||
},
|
|
||||||
"umbrella": {
|
|
||||||
"level": "带伞",
|
|
||||||
"desc": "有降水,带雨伞,短期外出可收起雨伞。"
|
|
||||||
},
|
|
||||||
"uv": {
|
|
||||||
"level": "最弱",
|
|
||||||
"desc": "辐射弱,涂擦SPF8-12防晒护肤品。"
|
|
||||||
},
|
|
||||||
"car_wash": {
|
|
||||||
"level": "不宜",
|
|
||||||
"desc": "有雨,雨水和泥水会弄脏爱车。"
|
|
||||||
},
|
|
||||||
"travel": {
|
|
||||||
"level": "适宜",
|
|
||||||
"desc": "较弱降水和微风将伴您共赴旅程。"
|
|
||||||
},
|
|
||||||
"sport": {
|
|
||||||
"level": "较不宜",
|
|
||||||
"desc": "有降水,推荐您在室内进行休闲运动。"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,132 +0,0 @@
|
|||||||
/* 背景样式文件 - 独立分离便于迁移 */
|
|
||||||
|
|
||||||
/* 主背景渐变 */
|
|
||||||
body {
|
|
||||||
background: linear-gradient(135deg,
|
|
||||||
#e8f5e8 0%,
|
|
||||||
#f1f8e9 25%,
|
|
||||||
#e8f5e8 50%,
|
|
||||||
#c8e6c9 75%,
|
|
||||||
#e8f5e8 100%);
|
|
||||||
background-size: 400% 400%;
|
|
||||||
animation: backgroundShift 15s ease-in-out infinite;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 背景动画 */
|
|
||||||
@keyframes backgroundShift {
|
|
||||||
0%, 100% {
|
|
||||||
background-position: 0% 50%;
|
|
||||||
}
|
|
||||||
25% {
|
|
||||||
background-position: 100% 50%;
|
|
||||||
}
|
|
||||||
50% {
|
|
||||||
background-position: 50% 100%;
|
|
||||||
}
|
|
||||||
75% {
|
|
||||||
background-position: 50% 0%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 背景装饰元素 */
|
|
||||||
body::before {
|
|
||||||
content: '';
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background-image:
|
|
||||||
radial-gradient(circle at 20% 20%, rgba(76, 175, 80, 0.1) 0%, transparent 50%),
|
|
||||||
radial-gradient(circle at 80% 80%, rgba(129, 199, 132, 0.1) 0%, transparent 50%),
|
|
||||||
radial-gradient(circle at 40% 60%, rgba(165, 214, 167, 0.08) 0%, transparent 50%);
|
|
||||||
pointer-events: none;
|
|
||||||
z-index: -1;
|
|
||||||
animation: floatingBubbles 20s ease-in-out infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes floatingBubbles {
|
|
||||||
0%, 100% {
|
|
||||||
transform: translateY(0px) rotate(0deg);
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
33% {
|
|
||||||
transform: translateY(-20px) rotate(120deg);
|
|
||||||
opacity: 0.8;
|
|
||||||
}
|
|
||||||
66% {
|
|
||||||
transform: translateY(10px) rotate(240deg);
|
|
||||||
opacity: 0.9;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 背景粒子效果 */
|
|
||||||
body::after {
|
|
||||||
content: '';
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background-image:
|
|
||||||
radial-gradient(2px 2px at 20px 30px, rgba(76, 175, 80, 0.3), transparent),
|
|
||||||
radial-gradient(2px 2px at 40px 70px, rgba(129, 199, 132, 0.2), transparent),
|
|
||||||
radial-gradient(1px 1px at 90px 40px, rgba(165, 214, 167, 0.3), transparent),
|
|
||||||
radial-gradient(1px 1px at 130px 80px, rgba(76, 175, 80, 0.2), transparent),
|
|
||||||
radial-gradient(2px 2px at 160px 30px, rgba(129, 199, 132, 0.3), transparent);
|
|
||||||
background-repeat: repeat;
|
|
||||||
background-size: 200px 100px;
|
|
||||||
pointer-events: none;
|
|
||||||
z-index: -1;
|
|
||||||
animation: particleFloat 25s linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes particleFloat {
|
|
||||||
0% {
|
|
||||||
transform: translateY(0px);
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
transform: translateY(-100px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 响应式背景调整 */
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
body::after {
|
|
||||||
background-size: 150px 75px;
|
|
||||||
animation-duration: 20s;
|
|
||||||
}
|
|
||||||
|
|
||||||
body::before {
|
|
||||||
animation-duration: 15s;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 480px) {
|
|
||||||
body::after {
|
|
||||||
background-size: 100px 50px;
|
|
||||||
animation-duration: 15s;
|
|
||||||
}
|
|
||||||
|
|
||||||
body::before {
|
|
||||||
animation-duration: 12s;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
animation-duration: 12s;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 高性能模式 - 减少动画 */
|
|
||||||
@media (prefers-reduced-motion: reduce) {
|
|
||||||
body,
|
|
||||||
body::before,
|
|
||||||
body::after {
|
|
||||||
animation: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
background: linear-gradient(135deg, #e8f5e8 0%, #f1f8e9 50%, #c8e6c9 100%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,468 +0,0 @@
|
|||||||
/* 基础样式重置 */
|
|
||||||
* {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
||||||
line-height: 1.6;
|
|
||||||
color: #2d5a3d;
|
|
||||||
min-height: 100vh;
|
|
||||||
overflow-x: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 容器样式 */
|
|
||||||
.container {
|
|
||||||
max-width: 1200px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 20px;
|
|
||||||
min-height: 100vh;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
position: relative;
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 头部样式 */
|
|
||||||
.header {
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 40px;
|
|
||||||
padding: 40px 20px;
|
|
||||||
background: rgba(255, 255, 255, 0.95);
|
|
||||||
backdrop-filter: blur(20px);
|
|
||||||
border-radius: 20px;
|
|
||||||
box-shadow: 0 8px 32px rgba(76, 175, 80, 0.1);
|
|
||||||
border: 1px solid rgba(76, 175, 80, 0.2);
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header::before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: -100%;
|
|
||||||
width: 100%;
|
|
||||||
height: 4px;
|
|
||||||
background: linear-gradient(90deg, transparent, #4caf50, transparent);
|
|
||||||
animation: headerGlow 3s ease-in-out infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes headerGlow {
|
|
||||||
0% { left: -100%; }
|
|
||||||
50% { left: 100%; }
|
|
||||||
100% { left: 100%; }
|
|
||||||
}
|
|
||||||
|
|
||||||
.header h1 {
|
|
||||||
font-size: 2.5rem;
|
|
||||||
font-weight: 700;
|
|
||||||
background: linear-gradient(135deg, #4caf50, #81c784, #4caf50);
|
|
||||||
background-size: 200% 200%;
|
|
||||||
-webkit-background-clip: text;
|
|
||||||
-webkit-text-fill-color: transparent;
|
|
||||||
background-clip: text;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
animation: titleGradient 4s ease-in-out infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes titleGradient {
|
|
||||||
0%, 100% { background-position: 0% 50%; }
|
|
||||||
50% { background-position: 100% 50%; }
|
|
||||||
}
|
|
||||||
|
|
||||||
.header p {
|
|
||||||
font-size: 1.1rem;
|
|
||||||
color: #66bb6a;
|
|
||||||
opacity: 0.9;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 主要内容区域 */
|
|
||||||
.main {
|
|
||||||
flex: 1;
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
gap: 40px;
|
|
||||||
align-items: start;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 表单容器 */
|
|
||||||
.form-container {
|
|
||||||
background: rgba(255, 255, 255, 0.95);
|
|
||||||
backdrop-filter: blur(20px);
|
|
||||||
padding: 35px;
|
|
||||||
border-radius: 20px;
|
|
||||||
box-shadow: 0 8px 32px rgba(76, 175, 80, 0.1);
|
|
||||||
border: 1px solid rgba(76, 175, 80, 0.2);
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-container:hover {
|
|
||||||
transform: translateY(-5px);
|
|
||||||
box-shadow: 0 12px 40px rgba(76, 175, 80, 0.15);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 表单样式 */
|
|
||||||
.qr-form {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 25px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-group {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-group label {
|
|
||||||
font-weight: 600;
|
|
||||||
color: #2d5a3d;
|
|
||||||
font-size: 0.95rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
textarea, select {
|
|
||||||
padding: 12px 16px;
|
|
||||||
border: 2px solid rgba(76, 175, 80, 0.3);
|
|
||||||
border-radius: 12px;
|
|
||||||
font-size: 1rem;
|
|
||||||
background: rgba(255, 255, 255, 0.9);
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
resize: vertical;
|
|
||||||
}
|
|
||||||
|
|
||||||
textarea {
|
|
||||||
min-height: 100px;
|
|
||||||
font-family: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
textarea:focus, select:focus {
|
|
||||||
outline: none;
|
|
||||||
border-color: #4caf50;
|
|
||||||
box-shadow: 0 0 0 3px rgba(76, 175, 80, 0.1);
|
|
||||||
background: rgba(255, 255, 255, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.options-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
|
||||||
gap: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 按钮样式 */
|
|
||||||
.generate-btn {
|
|
||||||
padding: 15px 30px;
|
|
||||||
background: linear-gradient(135deg, #4caf50, #66bb6a);
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
border-radius: 12px;
|
|
||||||
font-size: 1.1rem;
|
|
||||||
font-weight: 600;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
letter-spacing: 0.5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.generate-btn::before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: -100%;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
|
|
||||||
transition: left 0.5s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.generate-btn:hover::before {
|
|
||||||
left: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.generate-btn:hover {
|
|
||||||
transform: translateY(-2px);
|
|
||||||
box-shadow: 0 8px 25px rgba(76, 175, 80, 0.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
.generate-btn:active {
|
|
||||||
transform: translateY(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
.generate-btn.loading .btn-text {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.generate-btn.loading .btn-loading {
|
|
||||||
display: inline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-loading {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 结果容器 */
|
|
||||||
.result-container {
|
|
||||||
background: rgba(255, 255, 255, 0.95);
|
|
||||||
backdrop-filter: blur(20px);
|
|
||||||
padding: 35px;
|
|
||||||
border-radius: 20px;
|
|
||||||
box-shadow: 0 8px 32px rgba(76, 175, 80, 0.1);
|
|
||||||
border: 1px solid rgba(76, 175, 80, 0.2);
|
|
||||||
min-height: 400px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 加载动画 */
|
|
||||||
.loading {
|
|
||||||
text-align: center;
|
|
||||||
color: #4caf50;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading-spinner {
|
|
||||||
width: 50px;
|
|
||||||
height: 50px;
|
|
||||||
border: 4px solid rgba(76, 175, 80, 0.2);
|
|
||||||
border-top: 4px solid #4caf50;
|
|
||||||
border-radius: 50%;
|
|
||||||
animation: spin 1s linear infinite;
|
|
||||||
margin: 0 auto 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes spin {
|
|
||||||
0% { transform: rotate(0deg); }
|
|
||||||
100% { transform: rotate(360deg); }
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 错误样式 */
|
|
||||||
.error {
|
|
||||||
text-align: center;
|
|
||||||
color: #d32f2f;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-icon {
|
|
||||||
font-size: 3rem;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-message {
|
|
||||||
font-size: 1.1rem;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
|
|
||||||
.retry-btn {
|
|
||||||
padding: 10px 20px;
|
|
||||||
background: #4caf50;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
border-radius: 8px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.retry-btn:hover {
|
|
||||||
background: #45a049;
|
|
||||||
transform: translateY(-1px);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 结果显示 */
|
|
||||||
.result {
|
|
||||||
width: 100%;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.qr-display {
|
|
||||||
margin-bottom: 25px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.qr-display img {
|
|
||||||
max-width: 100%;
|
|
||||||
height: auto;
|
|
||||||
border-radius: 12px;
|
|
||||||
box-shadow: 0 4px 20px rgba(76, 175, 80, 0.2);
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.qr-display img:hover {
|
|
||||||
transform: scale(1.05);
|
|
||||||
box-shadow: 0 8px 30px rgba(76, 175, 80, 0.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
.result-info {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.result-text {
|
|
||||||
font-size: 1rem;
|
|
||||||
color: #666;
|
|
||||||
word-break: break-all;
|
|
||||||
background: rgba(76, 175, 80, 0.1);
|
|
||||||
padding: 12px;
|
|
||||||
border-radius: 8px;
|
|
||||||
border-left: 4px solid #4caf50;
|
|
||||||
}
|
|
||||||
|
|
||||||
.result-actions {
|
|
||||||
display: flex;
|
|
||||||
gap: 10px;
|
|
||||||
justify-content: center;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.download-btn, .copy-btn, .new-btn {
|
|
||||||
padding: 10px 16px;
|
|
||||||
border: none;
|
|
||||||
border-radius: 8px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
font-weight: 500;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.download-btn {
|
|
||||||
background: #4caf50;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.copy-btn {
|
|
||||||
background: #2196f3;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.new-btn {
|
|
||||||
background: #ff9800;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.download-btn:hover, .copy-btn:hover, .new-btn:hover {
|
|
||||||
transform: translateY(-1px);
|
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 隐藏类 */
|
|
||||||
.hidden {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 页脚样式 */
|
|
||||||
.footer {
|
|
||||||
text-align: center;
|
|
||||||
margin-top: 40px;
|
|
||||||
padding: 25px;
|
|
||||||
background: rgba(255, 255, 255, 0.9);
|
|
||||||
backdrop-filter: blur(15px);
|
|
||||||
border-radius: 15px;
|
|
||||||
color: #66bb6a;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
border: 1px solid rgba(76, 175, 80, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 平板端适配 (768px - 1024px) */
|
|
||||||
@media (max-width: 1024px) and (min-width: 768px) {
|
|
||||||
.container {
|
|
||||||
max-width: 95%;
|
|
||||||
padding: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main {
|
|
||||||
gap: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header h1 {
|
|
||||||
font-size: 2.2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-container, .result-container {
|
|
||||||
padding: 25px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.options-grid {
|
|
||||||
grid-template-columns: repeat(2, 1fr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 手机端适配 (最大768px) */
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.container {
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header {
|
|
||||||
margin-bottom: 25px;
|
|
||||||
padding: 25px 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header h1 {
|
|
||||||
font-size: 1.8rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header p {
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
gap: 25px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-container, .result-container {
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.options-grid {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
gap: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.result-actions {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.download-btn, .copy-btn, .new-btn {
|
|
||||||
width: 100%;
|
|
||||||
max-width: 200px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 小屏手机适配 (最大480px) */
|
|
||||||
@media (max-width: 480px) {
|
|
||||||
.container {
|
|
||||||
padding: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header {
|
|
||||||
padding: 20px 10px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header h1 {
|
|
||||||
font-size: 1.6rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-container, .result-container {
|
|
||||||
padding: 15px;
|
|
||||||
border-radius: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.generate-btn {
|
|
||||||
padding: 12px 20px;
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
textarea {
|
|
||||||
min-height: 80px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.qr-display img {
|
|
||||||
max-width: 90%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user