Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fe79c20d2f | ||
|
|
e08511d860 | ||
|
|
f07e99430f | ||
|
|
9794e48a81 |
@@ -1,36 +0,0 @@
|
||||
# Node modules
|
||||
InfoGenie-frontend/node_modules
|
||||
InfoGenie-frontend/build
|
||||
|
||||
# Python cache
|
||||
InfoGenie-backend/__pycache__
|
||||
InfoGenie-backend/**/__pycache__
|
||||
InfoGenie-backend/*.pyc
|
||||
InfoGenie-backend/**/*.pyc
|
||||
|
||||
# Git
|
||||
.git
|
||||
.gitignore
|
||||
|
||||
# IDE
|
||||
.vscode
|
||||
.idea
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Test files
|
||||
InfoGenie-backend/test
|
||||
|
||||
# Documentation
|
||||
*.md
|
||||
|
||||
# Backup files
|
||||
*.backup
|
||||
*.bak
|
||||
214
.gitignore
vendored
Executable file → Normal file
214
.gitignore
vendored
Executable file → Normal file
@@ -1,5 +1,209 @@
|
||||
#项目自忽略
|
||||
.vscode
|
||||
InfoGenie-frontend/node_modules
|
||||
InfoGenie-frontend/build
|
||||
InfoGenie-backend/__pycache__
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[codz]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py.cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# UV
|
||||
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
#uv.lock
|
||||
|
||||
# poetry
|
||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||
#poetry.lock
|
||||
#poetry.toml
|
||||
|
||||
# pdm
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
|
||||
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
|
||||
#pdm.lock
|
||||
#pdm.toml
|
||||
.pdm-python
|
||||
.pdm-build/
|
||||
|
||||
# pixi
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
|
||||
#pixi.lock
|
||||
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
|
||||
# in the .venv directory. It is recommended not to include this directory in version control.
|
||||
.pixi
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.envrc
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
# PyCharm
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
|
||||
# Abstra
|
||||
# Abstra is an AI-powered process automation framework.
|
||||
# Ignore directories containing user credentials, local state, and settings.
|
||||
# Learn more at https://abstra.io/docs
|
||||
.abstra/
|
||||
|
||||
# Visual Studio Code
|
||||
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
|
||||
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
||||
# you could uncomment the following to ignore the entire vscode folder
|
||||
# .vscode/
|
||||
|
||||
# Ruff stuff:
|
||||
.ruff_cache/
|
||||
|
||||
# PyPI configuration file
|
||||
.pypirc
|
||||
|
||||
# Cursor
|
||||
# Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
|
||||
# exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
|
||||
# refer to https://docs.cursor.com/context/ignore-files
|
||||
.cursorignore
|
||||
.cursorindexingignore
|
||||
|
||||
# Marimo
|
||||
marimo/_static/
|
||||
marimo/_lsp/
|
||||
__marimo__/
|
||||
|
||||
frontend/react-app/node_modules/
|
||||
|
||||
3
.vscode/settings.json
vendored
Executable file → Normal file
3
.vscode/settings.json
vendored
Executable file → Normal file
@@ -1,4 +1,3 @@
|
||||
{
|
||||
"git.ignoreLimitWarning": true,
|
||||
"terminal.integrated.defaultProfile.windows": "Command Prompt"
|
||||
"git.ignoreLimitWarning": true
|
||||
}
|
||||
57
Dockerfile
57
Dockerfile
@@ -1,57 +0,0 @@
|
||||
# InfoGenie 统一 Docker 镜像
|
||||
# 多阶段构建:前端构建 + 后端 + Nginx
|
||||
|
||||
# 阶段1: 前端构建
|
||||
FROM node:18-alpine AS frontend-builder
|
||||
|
||||
WORKDIR /frontend
|
||||
COPY InfoGenie-frontend/package*.json ./
|
||||
RUN npm install --legacy-peer-deps
|
||||
COPY InfoGenie-frontend/ ./
|
||||
RUN npm run build
|
||||
|
||||
# 阶段2: 最终镜像
|
||||
FROM python:3.10-slim
|
||||
|
||||
# 安装 Nginx 和必要的工具
|
||||
RUN apt-get update && apt-get install -y \
|
||||
nginx \
|
||||
supervisor \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# 设置工作目录
|
||||
WORKDIR /app
|
||||
|
||||
# 复制后端代码
|
||||
COPY InfoGenie-backend/ ./backend/
|
||||
|
||||
# 安装 Python 依赖
|
||||
RUN pip install --no-cache-dir -r ./backend/requirements.txt gunicorn
|
||||
|
||||
# 复制前端构建产物到 Nginx 目录
|
||||
COPY --from=frontend-builder /frontend/build /usr/share/nginx/html
|
||||
|
||||
# 创建持久化数据目录
|
||||
RUN mkdir -p /app/data/logs
|
||||
|
||||
# 复制 Nginx 配置
|
||||
COPY docker/nginx.conf /etc/nginx/nginx.conf
|
||||
COPY docker/default.conf /etc/nginx/conf.d/default.conf
|
||||
|
||||
# 复制 Supervisor 配置
|
||||
COPY docker/supervisord.conf /etc/supervisor/conf.d/supervisord.conf
|
||||
|
||||
# 复制启动脚本
|
||||
COPY docker/entrypoint.sh /entrypoint.sh
|
||||
RUN chmod +x /entrypoint.sh
|
||||
|
||||
# 暴露端口
|
||||
EXPOSE 2323
|
||||
|
||||
# 设置环境变量
|
||||
ENV FLASK_APP=app.py
|
||||
ENV FLASK_ENV=production
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
|
||||
# 使用 supervisor 管理多进程
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
1
InfoGenie
Submodule
1
InfoGenie
Submodule
Submodule InfoGenie added at dd43157e09
@@ -1,15 +0,0 @@
|
||||
# InfoGenie 环境变量配置文件
|
||||
# 请勿将此文件提交到版本控制系统
|
||||
|
||||
# 邮件配置
|
||||
MAIL_USERNAME=shumengya888@foxmail.com
|
||||
MAIL_PASSWORD=dpdouefloajfdagd
|
||||
|
||||
# 数据库配置
|
||||
MONGO_URI=mongodb://shumengya:shumengya520@47.108.90.0:27017/InfoGenie?authSource=admin
|
||||
|
||||
# 应用密钥
|
||||
SECRET_KEY=infogenie-secret-key-2025
|
||||
|
||||
# 环境配置
|
||||
FLASK_ENV=development
|
||||
@@ -1,13 +0,0 @@
|
||||
{
|
||||
"deepseek": {
|
||||
"api_key": "sk-832f8e5250464de08a31523c7fd712",
|
||||
"api_base": "https://api.deepseek.com",
|
||||
"model": ["deepseek-chat","deepseek-reasoner"]
|
||||
},
|
||||
|
||||
"kimi": {
|
||||
"api_key": "sk-zdg9NBpTlhOcDDpoWfaBKu0KNDdGv18SipORnL2utawja",
|
||||
"api_base": "https://api.moonshot.cn",
|
||||
"model": ["kimi-k2-0905-preview","kimi-k2-0711-preview"]
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -1,550 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
用户管理模块
|
||||
Created by: 万象口袋
|
||||
Date: 2025-09-02
|
||||
"""
|
||||
|
||||
from flask import Blueprint, request, jsonify, current_app
|
||||
from datetime import datetime
|
||||
from bson import ObjectId
|
||||
import jwt
|
||||
from functools import wraps
|
||||
|
||||
user_bp = Blueprint('user', __name__)
|
||||
|
||||
# 验证JWT token
|
||||
def verify_token(token):
|
||||
"""验证JWT token"""
|
||||
try:
|
||||
payload = jwt.decode(token, current_app.config['SECRET_KEY'], algorithms=['HS256'])
|
||||
return {'success': True, 'data': payload}
|
||||
except jwt.ExpiredSignatureError:
|
||||
return {'success': False, 'message': 'Token已过期'}
|
||||
except jwt.InvalidTokenError:
|
||||
return {'success': False, 'message': 'Token无效'}
|
||||
|
||||
# 登录验证装饰器(支持JWT token和hwt)
|
||||
def login_required(f):
|
||||
"""登录验证装饰器(支持JWT token和hwt)"""
|
||||
@wraps(f)
|
||||
def decorated_function(*args, **kwargs):
|
||||
# 优先检查JWT token
|
||||
token = request.headers.get('Authorization')
|
||||
if token:
|
||||
if token.startswith('Bearer '):
|
||||
token = token[7:]
|
||||
|
||||
result = verify_token(token)
|
||||
if result['success']:
|
||||
request.current_user = result['data']
|
||||
return f(*args, **kwargs)
|
||||
# 回退到hwt验证
|
||||
hwt = getattr(request, 'hwt', {})
|
||||
if not hwt.get('logged_in'):
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '请先登录'
|
||||
}), 401
|
||||
return f(*args, **kwargs)
|
||||
return decorated_function
|
||||
return decorated_function
|
||||
|
||||
|
||||
#==========================对外暴露的HTTP接口==========================
|
||||
# 获取用户资料
|
||||
@user_bp.route('/profile', methods=['GET'])
|
||||
@login_required
|
||||
def get_profile():
|
||||
"""获取用户资料"""
|
||||
try:
|
||||
# 优先从JWT token获取用户信息
|
||||
user_id = None
|
||||
if hasattr(request, 'current_user') and request.current_user:
|
||||
user_id = request.current_user.get('user_id')
|
||||
else:
|
||||
# 回退到hwt验证
|
||||
hwt = getattr(request, 'hwt', {})
|
||||
user_id = hwt.get('user_id')
|
||||
|
||||
if not user_id:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '无法获取用户信息'
|
||||
}), 401
|
||||
|
||||
users_collection = current_app.mongo.db.userdata
|
||||
user = users_collection.find_one({'_id': ObjectId(user_id)})
|
||||
if not user:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '用户不存在'
|
||||
}), 404
|
||||
# 返回用户信息(不包含密码)
|
||||
profile = {
|
||||
'account': user.get('邮箱'),
|
||||
'username': user.get('用户名'),
|
||||
'avatar': user.get('头像'),
|
||||
'register_time': user.get('注册时间'),
|
||||
'last_login': user.get('最后登录'),
|
||||
'login_count': user.get('登录次数', 0),
|
||||
'status': user.get('用户状态', 'active'),
|
||||
'level': user.get('等级', 1),
|
||||
'experience': user.get('经验', 0),
|
||||
'coins': user.get('萌芽币', 0)
|
||||
}
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'data': profile
|
||||
}), 200
|
||||
except Exception as e:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': f'服务器错误: {str(e)}'
|
||||
}), 500
|
||||
|
||||
# 为指定账号增加萌芽币
|
||||
@user_bp.route('/add-coins', methods=['POST'])
|
||||
@login_required
|
||||
def add_coins():
|
||||
"""为指定账号增加萌芽币(支持email或username指定账号,amount为正整数)"""
|
||||
try:
|
||||
data = request.get_json() or {}
|
||||
email = (data.get('email') or '').strip()
|
||||
username = (data.get('username') or '').strip()
|
||||
amount = data.get('amount')
|
||||
|
||||
# 参数校验
|
||||
if not email and not username:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '请提供email或username其中之一'
|
||||
}), 400
|
||||
|
||||
if amount is None:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': 'amount不能为空'
|
||||
}), 400
|
||||
|
||||
try:
|
||||
amount_int = int(amount)
|
||||
except Exception:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': 'amount必须为整数'
|
||||
}), 400
|
||||
|
||||
if amount_int <= 0:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': 'amount必须为正整数'
|
||||
}), 400
|
||||
|
||||
users_collection = current_app.mongo.db.userdata
|
||||
query = {'邮箱': email} if email else {'用户名': username}
|
||||
user = users_collection.find_one(query)
|
||||
if not user:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '用户不存在'
|
||||
}), 404
|
||||
|
||||
before_coins = user.get('萌芽币', 0)
|
||||
update_result = users_collection.update_one(query, {'$inc': {'萌芽币': amount_int}})
|
||||
|
||||
if update_result.modified_count == 0:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '更新失败,请稍后重试'
|
||||
}), 500
|
||||
|
||||
updated = users_collection.find_one({'_id': user['_id']})
|
||||
new_coins = updated.get('萌芽币', before_coins)
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': f"已为账户{email or username}增加{amount_int}萌芽币",
|
||||
'data': {
|
||||
'before_coins': before_coins,
|
||||
'added': amount_int,
|
||||
'new_coins': new_coins,
|
||||
'user': {
|
||||
'id': str(updated.get('_id')),
|
||||
'email': updated.get('邮箱'),
|
||||
'username': updated.get('用户名'),
|
||||
'avatar': updated.get('头像'),
|
||||
'register_time': updated.get('注册时间'),
|
||||
'last_login': updated.get('最后登录'),
|
||||
'login_count': updated.get('登录次数', 0),
|
||||
'status': updated.get('用户状态', 'active'),
|
||||
'level': updated.get('等级', 0),
|
||||
'experience': updated.get('经验', 0),
|
||||
'coins': new_coins
|
||||
}
|
||||
}
|
||||
}), 200
|
||||
except Exception as e:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': f'服务器错误: {str(e)}'
|
||||
}), 500
|
||||
|
||||
# 列出所有用户
|
||||
@user_bp.route('/list', methods=['GET'])
|
||||
@login_required
|
||||
def list_users():
|
||||
"""列出所有用户(不返回密码)"""
|
||||
try:
|
||||
users_collection = current_app.mongo.db.userdata
|
||||
cursor = users_collection.find({}, {'密码': 0})
|
||||
users = []
|
||||
for u in cursor:
|
||||
users.append({
|
||||
'id': str(u.get('_id')),
|
||||
'email': u.get('邮箱'),
|
||||
'username': u.get('用户名'),
|
||||
'avatar': u.get('头像'),
|
||||
'register_time': u.get('注册时间'),
|
||||
'last_login': u.get('最后登录'),
|
||||
'login_count': u.get('登录次数', 0),
|
||||
'status': u.get('用户状态', 'active'),
|
||||
'level': u.get('等级', 0),
|
||||
'experience': u.get('经验', 0),
|
||||
'coins': u.get('萌芽币', 0)
|
||||
})
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'count': len(users),
|
||||
'data': users
|
||||
}), 200
|
||||
except Exception as e:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': f'服务器错误: {str(e)}'
|
||||
}), 500
|
||||
|
||||
# 修改密码
|
||||
@user_bp.route('/change-password', methods=['POST'])
|
||||
@login_required
|
||||
def change_password():
|
||||
"""修改密码"""
|
||||
try:
|
||||
data = request.get_json()
|
||||
old_password = data.get('old_password', '').strip()
|
||||
new_password = data.get('new_password', '').strip()
|
||||
|
||||
if not old_password or not new_password:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '旧密码和新密码不能为空'
|
||||
}), 400
|
||||
|
||||
if len(new_password) < 6 or len(new_password) > 20:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '新密码长度必须在6-20位之间'
|
||||
}), 400
|
||||
|
||||
hwt = getattr(request, 'hwt', {})
|
||||
user_id = hwt.get('user_id')
|
||||
users_collection = current_app.mongo.db.userdata
|
||||
user = users_collection.find_one({'_id': ObjectId(user_id)})
|
||||
if not user:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '用户不存在'
|
||||
}), 404
|
||||
from werkzeug.security import check_password_hash, generate_password_hash
|
||||
# 验证旧密码
|
||||
if not check_password_hash(user['密码'], old_password):
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '原密码错误'
|
||||
}), 401
|
||||
# 更新密码
|
||||
new_password_hash = generate_password_hash(new_password)
|
||||
result = users_collection.update_one(
|
||||
{'_id': ObjectId(user_id)},
|
||||
{'$set': {'密码': new_password_hash}}
|
||||
)
|
||||
if result.modified_count > 0:
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': '密码修改成功'
|
||||
}), 200
|
||||
else:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '密码修改失败'
|
||||
}), 500
|
||||
except Exception as e:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': f'服务器错误: {str(e)}'
|
||||
}), 500
|
||||
|
||||
# 获取用户统计信息
|
||||
@user_bp.route('/stats', methods=['GET'])
|
||||
@login_required
|
||||
def get_user_stats():
|
||||
"""获取用户统计信息"""
|
||||
try:
|
||||
hwt = getattr(request, 'hwt', {})
|
||||
user_id = hwt.get('user_id')
|
||||
# 这里可以添加更多统计信息,比如API调用次数等
|
||||
stats = {
|
||||
'login_today': 1, # 今日登录次数
|
||||
'api_calls_today': 0, # 今日API调用次数
|
||||
'total_api_calls': 0, # 总API调用次数
|
||||
'join_days': 1, # 加入天数
|
||||
'last_activity': datetime.now().isoformat()
|
||||
}
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'data': stats
|
||||
}), 200
|
||||
except Exception as e:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': f'服务器错误: {str(e)}'
|
||||
}), 500
|
||||
|
||||
# 获取用户游戏数据
|
||||
@user_bp.route('/game-data', methods=['GET'])
|
||||
@login_required
|
||||
def get_user_game_data():
|
||||
"""获取用户游戏数据"""
|
||||
try:
|
||||
# 优先从JWT token获取用户ID
|
||||
if hasattr(request, 'current_user'):
|
||||
user_id = request.current_user['user_id']
|
||||
else:
|
||||
hwt = getattr(request, 'hwt', {})
|
||||
user_id = hwt.get('user_id')
|
||||
|
||||
users_collection = current_app.mongo.db.userdata
|
||||
|
||||
user = users_collection.find_one({'_id': ObjectId(user_id)})
|
||||
|
||||
if not user:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '用户不存在'
|
||||
}), 404
|
||||
|
||||
# 返回用户游戏数据
|
||||
game_data = {
|
||||
'level': user.get('等级', 0),
|
||||
'experience': user.get('经验', 0),
|
||||
'coins': user.get('萌芽币', 0),
|
||||
'checkin_system': user.get('签到系统', {
|
||||
'连续签到天数': 0,
|
||||
'今日是否已签到': False,
|
||||
'签到时间': '2025-01-01'
|
||||
})
|
||||
}
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'data': game_data
|
||||
}), 200
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': f'服务器错误: {str(e)}'
|
||||
}), 500
|
||||
|
||||
# 每日签到
|
||||
@user_bp.route('/checkin', methods=['POST'])
|
||||
@login_required
|
||||
def daily_checkin():
|
||||
"""每日签到"""
|
||||
try:
|
||||
# 优先从JWT token获取用户ID
|
||||
if hasattr(request, 'current_user'):
|
||||
user_id = request.current_user['user_id']
|
||||
else:
|
||||
hwt = getattr(request, 'hwt', {})
|
||||
user_id = hwt.get('user_id')
|
||||
|
||||
users_collection = current_app.mongo.db.userdata
|
||||
|
||||
user = users_collection.find_one({'_id': ObjectId(user_id)})
|
||||
|
||||
if not user:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '用户不存在'
|
||||
}), 404
|
||||
|
||||
# 获取当前日期
|
||||
today = datetime.now().strftime('%Y-%m-%d')
|
||||
|
||||
# 获取签到系统数据
|
||||
checkin_system = user.get('签到系统', {
|
||||
'连续签到天数': 0,
|
||||
'今日是否已签到': False,
|
||||
'签到时间': '2025-01-01'
|
||||
})
|
||||
|
||||
# 检查今日是否已签到
|
||||
if checkin_system.get('今日是否已签到', False) and checkin_system.get('签到时间') == today:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '今日已签到,请明天再来!'
|
||||
}), 400
|
||||
|
||||
# 计算连续签到天数
|
||||
last_checkin_date = checkin_system.get('签到时间', '2025-01-01')
|
||||
consecutive_days = checkin_system.get('连续签到天数', 0)
|
||||
|
||||
# 检查是否连续签到
|
||||
if last_checkin_date:
|
||||
try:
|
||||
last_date = datetime.strptime(last_checkin_date, '%Y-%m-%d')
|
||||
today_date = datetime.strptime(today, '%Y-%m-%d')
|
||||
days_diff = (today_date - last_date).days
|
||||
|
||||
if days_diff == 1:
|
||||
# 连续签到
|
||||
consecutive_days += 1
|
||||
elif days_diff > 1:
|
||||
# 断签,重新开始
|
||||
consecutive_days = 1
|
||||
else:
|
||||
# 同一天,不应该发生
|
||||
consecutive_days = consecutive_days
|
||||
except:
|
||||
consecutive_days = 1
|
||||
else:
|
||||
consecutive_days = 1
|
||||
|
||||
# 签到奖励
|
||||
coin_reward = 300
|
||||
exp_reward = 200
|
||||
|
||||
# 获取当前用户数据
|
||||
current_coins = user.get('萌芽币', 0)
|
||||
current_exp = user.get('经验', 0)
|
||||
current_level = user.get('等级', 0)
|
||||
|
||||
# 计算新的经验和等级
|
||||
new_exp = current_exp + exp_reward
|
||||
new_level = current_level
|
||||
|
||||
# 等级升级逻辑:100 × 1.2^(等级)
|
||||
while True:
|
||||
exp_needed = int(100 * (1.2 ** new_level))
|
||||
if new_exp >= exp_needed:
|
||||
new_exp -= exp_needed
|
||||
new_level += 1
|
||||
else:
|
||||
break
|
||||
|
||||
# 更新用户数据
|
||||
update_data = {
|
||||
'萌芽币': current_coins + coin_reward,
|
||||
'经验': new_exp,
|
||||
'等级': new_level,
|
||||
'签到系统': {
|
||||
'连续签到天数': consecutive_days,
|
||||
'今日是否已签到': True,
|
||||
'签到时间': today
|
||||
}
|
||||
}
|
||||
|
||||
result = users_collection.update_one(
|
||||
{'_id': ObjectId(user_id)},
|
||||
{'$set': update_data}
|
||||
)
|
||||
|
||||
if result.modified_count > 0:
|
||||
level_up = new_level > current_level
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': '签到成功!',
|
||||
'data': {
|
||||
'coin_reward': coin_reward,
|
||||
'exp_reward': exp_reward,
|
||||
'consecutive_days': consecutive_days,
|
||||
'level_up': level_up,
|
||||
'new_level': new_level,
|
||||
'new_coins': current_coins + coin_reward,
|
||||
'new_exp': new_exp
|
||||
}
|
||||
}), 200
|
||||
else:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '签到失败,请稍后重试'
|
||||
}), 500
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': f'服务器错误: {str(e)}'
|
||||
}), 500
|
||||
|
||||
# 删除账户
|
||||
@user_bp.route('/delete', methods=['POST'])
|
||||
@login_required
|
||||
def delete_account():
|
||||
"""删除账户"""
|
||||
try:
|
||||
data = request.get_json()
|
||||
password = data.get('password', '').strip()
|
||||
|
||||
if not password:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '请输入密码确认删除'
|
||||
}), 400
|
||||
|
||||
hwt = getattr(request, 'hwt', {})
|
||||
user_id = hwt.get('user_id')
|
||||
users_collection = current_app.mongo.db.userdata
|
||||
|
||||
user = users_collection.find_one({'_id': ObjectId(user_id)})
|
||||
|
||||
if not user:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '用户不存在'
|
||||
}), 404
|
||||
|
||||
from werkzeug.security import check_password_hash
|
||||
|
||||
# 验证密码
|
||||
if not check_password_hash(user['密码'], password):
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '密码错误'
|
||||
}), 401
|
||||
|
||||
# 删除用户
|
||||
result = users_collection.delete_one({'_id': ObjectId(user_id)})
|
||||
|
||||
if result.deleted_count > 0:
|
||||
# 清除会话
|
||||
hwt = getattr(request, 'hwt', {})
|
||||
hwt.clear()
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': '账户已成功删除'
|
||||
}), 200
|
||||
else:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '删除失败'
|
||||
}), 500
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': f'服务器错误: {str(e)}'
|
||||
}), 500
|
||||
#==========================对外暴露的HTTP接口==========================
|
||||
@@ -1,2 +0,0 @@
|
||||
@echo off
|
||||
python app.py
|
||||
@@ -1,2 +0,0 @@
|
||||
#!/bin/bash
|
||||
python3 app.py
|
||||
@@ -1,100 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
测试为指定账号增加萌芽币接口 (/api/user/add-coins)
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
from datetime import datetime
|
||||
|
||||
# 加入后端根目录到路径,导入create_app
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from app import create_app
|
||||
from modules.auth import generate_token
|
||||
from werkzeug.security import generate_password_hash
|
||||
|
||||
|
||||
def run_test():
|
||||
"""运行加币接口测试,打印真实响应并断言结果"""
|
||||
app = create_app()
|
||||
|
||||
with app.app_context():
|
||||
db = app.mongo.db
|
||||
users = db.userdata
|
||||
|
||||
# 构造一个临时测试用户(真实写库,测试结束删除)
|
||||
test_email = "infogenie.test.addcoins@foxmail.com"
|
||||
users.delete_many({'邮箱': test_email})
|
||||
test_user = {
|
||||
'邮箱': test_email,
|
||||
'用户名': '测试用户_加币',
|
||||
'密码': generate_password_hash('AddCoins123!'),
|
||||
'头像': None,
|
||||
'注册时间': datetime.now().isoformat(),
|
||||
'最后登录': None,
|
||||
'登录次数': 0,
|
||||
'用户状态': 'active',
|
||||
'等级': 0,
|
||||
'经验': 0,
|
||||
'萌芽币': 0,
|
||||
'签到系统': {
|
||||
'连续签到天数': 0,
|
||||
'今日是否已签到': False,
|
||||
'签到时间': datetime.now().strftime('%Y-%m-%d')
|
||||
}
|
||||
}
|
||||
insert_result = users.insert_one(test_user)
|
||||
test_user_id = str(insert_result.inserted_id)
|
||||
|
||||
# 生成有效JWT用于认证
|
||||
token = generate_token({
|
||||
'user_id': test_user_id,
|
||||
'email': test_email,
|
||||
'username': test_user['用户名']
|
||||
})
|
||||
|
||||
client = app.test_client()
|
||||
|
||||
# 第一次加币: +500
|
||||
resp1 = client.post(
|
||||
'/api/user/add-coins',
|
||||
headers={'Authorization': f'Bearer {token}'},
|
||||
json={'email': test_email, 'amount': 500}
|
||||
)
|
||||
print('第一次加币 状态码:', resp1.status_code)
|
||||
data1 = resp1.get_json()
|
||||
print('第一次加币 响应:')
|
||||
print(json.dumps(data1, ensure_ascii=False, indent=2))
|
||||
assert resp1.status_code == 200
|
||||
assert data1.get('success') is True
|
||||
assert data1['data']['before_coins'] == 0
|
||||
assert data1['data']['added'] == 500
|
||||
assert data1['data']['new_coins'] == 500
|
||||
|
||||
# 第二次加币: +200
|
||||
resp2 = client.post(
|
||||
'/api/user/add-coins',
|
||||
headers={'Authorization': f'Bearer {token}'},
|
||||
json={'email': test_email, 'amount': 200}
|
||||
)
|
||||
print('第二次加币 状态码:', resp2.status_code)
|
||||
data2 = resp2.get_json()
|
||||
print('第二次加币 响应:')
|
||||
print(json.dumps(data2, ensure_ascii=False, indent=2))
|
||||
assert resp2.status_code == 200
|
||||
assert data2.get('success') is True
|
||||
assert data2['data']['before_coins'] == 500
|
||||
assert data2['data']['added'] == 200
|
||||
assert data2['data']['new_coins'] == 700
|
||||
|
||||
# 清理临时测试用户
|
||||
users.delete_many({'邮箱': test_email})
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print('🔧 开始测试 /api/user/add-coins 接口...')
|
||||
run_test()
|
||||
print('✅ 测试完成!')
|
||||
@@ -1,81 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
测试列出所有用户的HTTP接口 (/api/user/list)
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
from datetime import datetime
|
||||
|
||||
# 将后端根目录加入路径,便于导入app
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from app import create_app
|
||||
from modules.auth import generate_token
|
||||
from werkzeug.security import generate_password_hash
|
||||
|
||||
|
||||
def run_test():
|
||||
"""运行用户列表接口测试,输出真实数据"""
|
||||
# 使用.env中的真实Mongo配置,不造假
|
||||
app = create_app()
|
||||
|
||||
with app.app_context():
|
||||
db = app.mongo.db
|
||||
users = db.userdata
|
||||
|
||||
# 插入一个测试用户(真实写入后再删除),确保可验证接口输出
|
||||
test_email = "infogenie.test.user@foxmail.com"
|
||||
users.delete_many({'邮箱': test_email})
|
||||
test_user = {
|
||||
'邮箱': test_email,
|
||||
'用户名': '测试用户_列表',
|
||||
'密码': generate_password_hash('TestPass123!'),
|
||||
'头像': None,
|
||||
'注册时间': datetime.now().isoformat(),
|
||||
'最后登录': None,
|
||||
'登录次数': 0,
|
||||
'用户状态': 'active',
|
||||
'等级': 0,
|
||||
'经验': 0,
|
||||
'萌芽币': 0,
|
||||
'签到系统': {
|
||||
'连续签到天数': 0,
|
||||
'今日是否已签到': False,
|
||||
'签到时间': datetime.now().strftime('%Y-%m-%d')
|
||||
}
|
||||
}
|
||||
insert_result = users.insert_one(test_user)
|
||||
test_user_id = str(insert_result.inserted_id)
|
||||
|
||||
# 生成有效JWT,满足认证要求
|
||||
token = generate_token({
|
||||
'user_id': test_user_id,
|
||||
'email': test_email,
|
||||
'username': test_user['用户名']
|
||||
})
|
||||
|
||||
client = app.test_client()
|
||||
resp = client.get('/api/user/list', headers={'Authorization': f'Bearer {token}'})
|
||||
|
||||
print("状态码:", resp.status_code)
|
||||
data = resp.get_json()
|
||||
print("响应内容:")
|
||||
print(json.dumps(data, ensure_ascii=False, indent=2))
|
||||
|
||||
# 基本断言,确保返回真实列表数据且包含刚插入的测试用户
|
||||
assert resp.status_code == 200
|
||||
assert data.get('success') is True
|
||||
assert isinstance(data.get('data'), list)
|
||||
assert any(u.get('email') == test_email for u in data['data'])
|
||||
|
||||
# 清理测试数据
|
||||
users.delete_many({'邮箱': test_email})
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print('🔎 开始测试 /api/user/list 接口...')
|
||||
run_test()
|
||||
print('✅ 测试完成!')
|
||||
@@ -1,396 +0,0 @@
|
||||
# InfoGenie 后端架构文档
|
||||
|
||||
## 项目概述
|
||||
|
||||
InfoGenie(万象口袋)是一个基于前后端分离架构的多功能聚合软件应用。后端采用Flask框架提供RESTful API服务,前端通过HTTP请求调用后端API,实现数据交互和业务逻辑处理。
|
||||
|
||||
## 技术栈
|
||||
|
||||
### 核心框架
|
||||
- **Web框架**: Flask 2.3.3
|
||||
- **数据库**: MongoDB (Flask-PyMongo 2.3.0)
|
||||
- **认证**: JWT (PyJWT 2.8.0)
|
||||
- **跨域**: Flask-CORS 4.0.0
|
||||
|
||||
### 辅助工具
|
||||
- **邮件服务**: Flask-Mail 0.9.1
|
||||
- **密码加密**: Werkzeug 2.3.7
|
||||
- **环境配置**: python-dotenv 1.0.0
|
||||
- **API限流**: Flask-Limiter 3.5.0
|
||||
|
||||
## 架构设计原则
|
||||
|
||||
### 前后端分离
|
||||
- 后端专注于数据处理和业务逻辑
|
||||
- 前端负责用户界面和交互体验
|
||||
- 通过RESTful API进行数据交换
|
||||
- 完全解耦,便于独立开发和部署
|
||||
|
||||
### 模块化设计
|
||||
- 按功能划分独立模块
|
||||
- 每个模块职责单一
|
||||
- 便于维护和扩展
|
||||
|
||||
## 核心模块详解
|
||||
|
||||
### 1. 认证模块 (auth.py)
|
||||
|
||||
**功能职责**:
|
||||
- 用户注册和登录
|
||||
- JWT Token生成和管理
|
||||
- 邮箱验证码验证
|
||||
- QQ邮箱格式验证
|
||||
|
||||
**API端点**:
|
||||
```
|
||||
POST /api/auth/send-verification # 发送验证码
|
||||
POST /api/auth/verify-code # 验证验证码
|
||||
POST /api/auth/register # 用户注册
|
||||
POST /api/auth/login # 用户登录
|
||||
POST /api/auth/logout # 用户登出
|
||||
GET /api/auth/check # 检查登录状态
|
||||
```
|
||||
|
||||
**数据流程**:
|
||||
1. 前端发送注册/登录请求
|
||||
2. 后端验证邮箱格式(仅支持QQ邮箱)
|
||||
3. 发送验证码邮件到用户邮箱
|
||||
4. 用户输入验证码完成验证
|
||||
5. 验证成功后生成JWT Token返回给前端
|
||||
|
||||
**安全特性**:
|
||||
- 密码使用Werkzeug进行哈希加密
|
||||
- JWT Token 7天有效期
|
||||
- 验证码5分钟有效期,限制尝试次数
|
||||
|
||||
### 2. 用户管理模块 (user_management.py)
|
||||
|
||||
**功能职责**:
|
||||
- 用户资料管理
|
||||
- 密码修改
|
||||
- 每日签到系统
|
||||
- 用户游戏数据管理
|
||||
- 账户删除
|
||||
|
||||
**API端点**:
|
||||
```
|
||||
GET /api/user/profile # 获取用户资料
|
||||
POST /api/user/change-password # 修改密码
|
||||
GET /api/user/stats # 获取用户统计
|
||||
GET /api/user/game-data # 获取游戏数据
|
||||
POST /api/user/checkin # 每日签到
|
||||
POST /api/user/delete # 删除账户
|
||||
```
|
||||
|
||||
**数据结构**:
|
||||
```json
|
||||
{
|
||||
"邮箱": "user@qq.com",
|
||||
"用户名": "用户名",
|
||||
"密码": "哈希密码",
|
||||
"头像": "QQ头像URL",
|
||||
"注册时间": "2025-01-01T00:00:00",
|
||||
"最后登录": "2025-01-01T00:00:00",
|
||||
"登录次数": 10,
|
||||
"用户状态": "active",
|
||||
"等级": 5,
|
||||
"经验": 1200,
|
||||
"萌芽币": 1500,
|
||||
"签到系统": {
|
||||
"连续签到天数": 7,
|
||||
"今日是否已签到": true,
|
||||
"签到时间": "2025-01-01"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**业务逻辑**:
|
||||
- 签到奖励:300萌芽币 + 200经验
|
||||
- 等级升级:100 × 1.2^(等级) 经验需求
|
||||
|
||||
### 3. 邮件服务模块 (email_service.py)
|
||||
|
||||
**功能职责**:
|
||||
- 验证码邮件发送
|
||||
- QQ邮箱格式验证
|
||||
- QQ头像获取
|
||||
- 邮件模板管理
|
||||
|
||||
**邮件模板**:
|
||||
- 注册验证码邮件(HTML格式)
|
||||
- 登录验证码邮件(HTML格式)
|
||||
- 支持自定义邮件内容和样式
|
||||
|
||||
**安全考虑**:
|
||||
- 仅支持QQ邮箱(qq.com、vip.qq.com、foxmail.com)
|
||||
- 使用SSL加密连接
|
||||
- 验证码存储在内存中(生产环境建议使用Redis)
|
||||
|
||||
### 4. AI模型应用模块 (aimodelapp.py)
|
||||
|
||||
**功能职责**:
|
||||
- 集成多种AI服务(DeepSeek、Kimi)
|
||||
- 提供AI功能API接口
|
||||
- 统一AI接口调用
|
||||
- 管理用户萌芽币消费(每次调用消耗100萌芽币)
|
||||
|
||||
**支持的AI功能**:
|
||||
1. **AI聊天接口** (`/api/aimodelapp/chat`)
|
||||
2. **姓名分析** (`/api/aimodelapp/name-analysis`)
|
||||
3. **变量命名助手** (`/api/aimodelapp/variable-naming`)
|
||||
4. **AI写诗助手** (`/api/aimodelapp/poetry`)
|
||||
5. **AI语言翻译** (`/api/aimodelapp/translation`)
|
||||
6. **现代文转文言文** (`/api/aimodelapp/classical_conversion`)
|
||||
7. **AI表情制作器** (`/api/aimodelapp/expression-maker`)
|
||||
8. **Linux命令生成** (`/api/aimodelapp/linux-command`)
|
||||
9. **获取可用模型** (`/api/aimodelapp/models`)
|
||||
|
||||
**AI配置**:
|
||||
```json
|
||||
{
|
||||
"deepseek": {
|
||||
"api_key": "your-api-key",
|
||||
"api_base": "https://api.deepseek.com",
|
||||
"model": ["deepseek-chat", "deepseek-reasoner"]
|
||||
},
|
||||
"kimi": {
|
||||
"api_key": "your-api-key",
|
||||
"api_base": "https://api.moonshot.cn",
|
||||
"model": ["kimi-k2-0905-preview", "kimi-k2-0711-preview"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**调用流程**:
|
||||
1. 前端发送AI请求(包含消息、模型提供商等参数)
|
||||
2. 后端加载AI配置文件
|
||||
3. 调用对应AI API(带重试机制)
|
||||
4. 返回AI响应给前端
|
||||
|
||||
## API设计规范
|
||||
|
||||
### 请求/响应格式
|
||||
|
||||
**成功响应**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {...},
|
||||
"message": "操作成功",
|
||||
"timestamp": "2025-01-01T00:00:00"
|
||||
}
|
||||
```
|
||||
|
||||
**错误响应**:
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"message": "错误信息",
|
||||
"error": "错误详情"
|
||||
}
|
||||
```
|
||||
|
||||
### 认证方式
|
||||
|
||||
**JWT Token认证**:
|
||||
```
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
**支持的认证端点**:
|
||||
- 所有 `/api/user/*` 端点需要认证
|
||||
- 部分 `/api/aimodelapp/*` 端点需要认证
|
||||
|
||||
### 错误处理
|
||||
|
||||
**HTTP状态码**:
|
||||
- 200: 成功
|
||||
- 400: 请求参数错误
|
||||
- 401: 未认证/认证失败
|
||||
- 403: 权限不足
|
||||
- 404: 资源不存在
|
||||
- 409: 资源冲突
|
||||
- 500: 服务器内部错误
|
||||
|
||||
## 数据库设计
|
||||
|
||||
### MongoDB集合
|
||||
|
||||
**主要集合**: `userdata`
|
||||
- 存储所有用户相关数据
|
||||
- 支持动态字段扩展
|
||||
- 使用ObjectId作为用户唯一标识
|
||||
|
||||
### 数据关系
|
||||
- 用户数据自包含,无复杂关联
|
||||
- 通过用户ID进行数据关联
|
||||
- 支持水平扩展
|
||||
|
||||
## 部署和配置
|
||||
|
||||
### 环境配置
|
||||
|
||||
**必需环境变量**:
|
||||
```
|
||||
SECRET_KEY=your-secret-key
|
||||
MONGO_URI=mongodb://localhost:27017/InfoGenie
|
||||
MAIL_USERNAME=your-email@qq.com
|
||||
MAIL_PASSWORD=your-app-password
|
||||
```
|
||||
|
||||
### 启动方式
|
||||
|
||||
**开发环境**:
|
||||
```bash
|
||||
python app.py
|
||||
```
|
||||
|
||||
**生产环境**:
|
||||
- 支持Docker部署
|
||||
- 提供docker-compose配置
|
||||
- 支持Gunicorn WSGI服务器
|
||||
|
||||
### 静态文件服务
|
||||
|
||||
**支持的前端资源**:
|
||||
- `/60sapi/*`: 60秒API相关文件
|
||||
- `/smallgame/*`: 小游戏相关文件
|
||||
- `/aimodelapp/*`: AI模型应用相关文件
|
||||
|
||||
## 安全考虑
|
||||
|
||||
### 数据安全
|
||||
- 密码哈希存储
|
||||
- JWT Token安全传输
|
||||
- 输入数据验证和过滤
|
||||
|
||||
### API安全
|
||||
- CORS配置(生产环境限制域名)
|
||||
- API限流保护
|
||||
- 请求日志记录
|
||||
|
||||
### 部署安全
|
||||
- 环境变量管理敏感信息
|
||||
- HTTPS证书配置
|
||||
- 防火墙和访问控制
|
||||
|
||||
## 前后端协作指南
|
||||
|
||||
### 前端调用示例
|
||||
|
||||
**用户登录**:
|
||||
```javascript
|
||||
// 1. 发送验证码
|
||||
fetch('/api/auth/send-verification', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ email: 'user@qq.com', type: 'login' })
|
||||
});
|
||||
|
||||
// 2. 验证验证码并登录
|
||||
fetch('/api/auth/login', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
email: 'user@qq.com',
|
||||
code: '123456'
|
||||
})
|
||||
});
|
||||
|
||||
// 3. 保存token到localStorage
|
||||
localStorage.setItem('token', response.token);
|
||||
```
|
||||
|
||||
**调用需要认证的API**:
|
||||
```javascript
|
||||
fetch('/api/user/profile', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${localStorage.getItem('token')}`
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### 数据约定
|
||||
|
||||
**前端发送数据格式**:
|
||||
- 所有请求使用JSON格式
|
||||
- 必填字段验证
|
||||
- 参数命名使用snake_case
|
||||
|
||||
**后端返回数据格式**:
|
||||
- 统一响应格式
|
||||
- 时间戳使用ISO格式
|
||||
- 错误信息清晰明确
|
||||
|
||||
### 开发协作流程
|
||||
|
||||
1. **API设计阶段**:
|
||||
- 后端定义API接口规范
|
||||
- 前端根据规范开发调用代码
|
||||
- 约定数据格式和错误处理
|
||||
|
||||
2. **联调阶段**:
|
||||
- 使用统一的测试数据
|
||||
- 验证各种边界情况
|
||||
- 确认错误处理逻辑
|
||||
|
||||
3. **部署阶段**:
|
||||
- 后端部署API服务
|
||||
- 前端配置API基础URL
|
||||
- 验证跨域和认证配置
|
||||
|
||||
## 新功能添加
|
||||
|
||||
### 1. AI功能萌芽币消费系统
|
||||
|
||||
**功能描述**:
|
||||
- 用户每次调用AI模型应用(aimodelapp)需消耗100萌芽币
|
||||
- 当用户萌芽币余额不足时,无法使用AI功能
|
||||
- 记录用户的AI使用历史
|
||||
|
||||
**API端点**:
|
||||
```
|
||||
GET /api/aimodelapp/coins # 查询用户萌芽币余额和使用历史
|
||||
```
|
||||
|
||||
**技术实现**:
|
||||
- 使用装饰器模式实现请求前验证和扣除萌芽币
|
||||
- 在MongoDB中记录用户AI使用历史
|
||||
- 通过JWT Token验证用户身份
|
||||
|
||||
**业务逻辑**:
|
||||
1. 当用户请求AI功能时,首先验证JWT Token
|
||||
2. 检查用户萌芽币余额是否≥100
|
||||
3. 如余额充足,先扣除萌芽币,然后再调用AI服务
|
||||
4. 记录使用历史,包括API类型、时间和消费萌芽币数量
|
||||
5. 返回AI服务结果给用户
|
||||
|
||||
**响应示例(查询萌芽币余额)**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"coins": 200,
|
||||
"ai_cost": 100,
|
||||
"can_use_ai": true,
|
||||
"username": "用户名",
|
||||
"usage_count": 1,
|
||||
"recent_usage": [
|
||||
{
|
||||
"api_type": "chat",
|
||||
"cost": 100,
|
||||
"timestamp": "2025-09-16T11:15:47.285720"
|
||||
}
|
||||
]
|
||||
},
|
||||
"message": "当前萌芽币余额: 200"
|
||||
}
|
||||
```
|
||||
|
||||
**前端开发注意事项**:
|
||||
- 每个需要调用AI功能的页面应首先检查用户萌芽币余额
|
||||
- 当萌芽币不足时,向用户提示并引导用户通过签到等方式获取萌芽币
|
||||
- 可在UI中展示用户最近的AI使用记录和萌芽币消费情况
|
||||
|
||||
---
|
||||
@@ -1,13 +0,0 @@
|
||||
{
|
||||
"账号":"3205788256",
|
||||
"邮箱":"3205788256@qq.com",
|
||||
"密码":"0123456789",
|
||||
"等级":0,
|
||||
"经验":0,
|
||||
"萌芽币":0,
|
||||
"签到系统":{
|
||||
"连续签到天数":0,
|
||||
"今日是否已签到":false,
|
||||
"签到时间":"2025-01-01"
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
# React 开发环境变量
|
||||
|
||||
# API URL - 开发环境使用本地后端
|
||||
REACT_APP_API_URL=http://127.0.0.1:5002
|
||||
|
||||
# 应用信息
|
||||
REACT_APP_NAME=InfoGenie
|
||||
REACT_APP_VERSION=1.0.0
|
||||
|
||||
# 调试模式
|
||||
REACT_APP_DEBUG=true
|
||||
@@ -1,13 +0,0 @@
|
||||
# React 构建时环境变量
|
||||
# 用于 Docker 构建
|
||||
|
||||
# API URL - 在 Docker 环境下,前端和后端在同一个容器
|
||||
# 使用相对路径,这样前端会自动使用当前域名
|
||||
REACT_APP_API_URL=
|
||||
|
||||
# 应用信息
|
||||
REACT_APP_NAME=InfoGenie
|
||||
REACT_APP_VERSION=1.0.0
|
||||
|
||||
# 调试模式(生产环境关闭)
|
||||
REACT_APP_DEBUG=false
|
||||
@@ -1,4 +0,0 @@
|
||||
@echo off
|
||||
npm run build
|
||||
npx serve -s build
|
||||
pause
|
||||
@@ -1,5 +0,0 @@
|
||||
# 生产环境API配置
|
||||
REACT_APP_API_URL=https://infogenie.api.shumengya.top
|
||||
|
||||
# 生产环境API配置
|
||||
REACT_APP_API_URL=http://127.0.0.1:5002
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"code": 200,
|
||||
"message": "获取成功。数据来自官方/权威源头,以确保稳定与实时。开源地址 https://github.com/vikiboss/60s,反馈群 595941841",
|
||||
"data": {
|
||||
"index": 78,
|
||||
"kfc": "我叫夯大力 立冬给我准备了糖炒栗子了没有 没准备的自动绝交 再 v 我 50 吃疯狂星期四 然后再给我点杯奶茶 再给我两万块钱 懂吗你们"
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
["https://60s.api.shumengya.top"]
|
||||
@@ -1 +0,0 @@
|
||||
["https://60s.api.shumengya.top"]
|
||||
@@ -1 +0,0 @@
|
||||
["https://60s.api.shumengya.top"]
|
||||
@@ -1,45 +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>📘真理之道</title>
|
||||
<meta name="description" content="当你踌躇不定,犹豫不决时,不妨来这里看看吧。" />
|
||||
|
||||
<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="answer-card">
|
||||
<div class="answer-text" id="answer">-</div>
|
||||
<div class="answer-en" id="answer-en" style="display: none;">-</div>
|
||||
<div class="meta">编号:<span id="index">-</span></div>
|
||||
<div class="actions">
|
||||
<button class="btn" id="refresh-btn">换一个</button>
|
||||
<button class="btn" id="copy-btn">复制</button>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="./js/script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,3 +0,0 @@
|
||||
[
|
||||
"https://60s.api.shumengya.top"
|
||||
]
|
||||
@@ -1,233 +0,0 @@
|
||||
/* 动态背景样式 */
|
||||
body::before {
|
||||
content: '';
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(135deg, #f0f8e8 0%, #e8f5e8 50%, #d4f4dd 100%);
|
||||
z-index: -2;
|
||||
}
|
||||
|
||||
body::after {
|
||||
content: '';
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background:
|
||||
radial-gradient(circle at 20% 80%, rgba(144, 238, 144, 0.2) 0%, transparent 50%),
|
||||
radial-gradient(circle at 80% 20%, rgba(173, 255, 173, 0.2) 0%, transparent 50%),
|
||||
radial-gradient(circle at 40% 40%, rgba(152, 251, 152, 0.2) 0%, transparent 50%);
|
||||
z-index: -1;
|
||||
animation: backgroundMove 20s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes backgroundMove {
|
||||
0%, 100% {
|
||||
background:
|
||||
radial-gradient(circle at 20% 80%, rgba(144, 238, 144, 0.2) 0%, transparent 50%),
|
||||
radial-gradient(circle at 80% 20%, rgba(173, 255, 173, 0.2) 0%, transparent 50%),
|
||||
radial-gradient(circle at 40% 40%, rgba(152, 251, 152, 0.2) 0%, transparent 50%);
|
||||
}
|
||||
25% {
|
||||
background:
|
||||
radial-gradient(circle at 60% 30%, rgba(144, 238, 144, 0.2) 0%, transparent 50%),
|
||||
radial-gradient(circle at 30% 70%, rgba(173, 255, 173, 0.2) 0%, transparent 50%),
|
||||
radial-gradient(circle at 80% 80%, rgba(152, 251, 152, 0.2) 0%, transparent 50%);
|
||||
}
|
||||
50% {
|
||||
background:
|
||||
radial-gradient(circle at 80% 60%, rgba(144, 238, 144, 0.2) 0%, transparent 50%),
|
||||
radial-gradient(circle at 20% 30%, rgba(173, 255, 173, 0.2) 0%, transparent 50%),
|
||||
radial-gradient(circle at 60% 70%, rgba(152, 251, 152, 0.2) 0%, transparent 50%);
|
||||
}
|
||||
75% {
|
||||
background:
|
||||
radial-gradient(circle at 40% 90%, rgba(144, 238, 144, 0.2) 0%, transparent 50%),
|
||||
radial-gradient(circle at 70% 10%, rgba(173, 255, 173, 0.2) 0%, transparent 50%),
|
||||
radial-gradient(circle at 20% 60%, rgba(152, 251, 152, 0.2) 0%, transparent 50%);
|
||||
}
|
||||
}
|
||||
|
||||
/* 浮动粒子效果 */
|
||||
.particles {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.particle {
|
||||
position: absolute;
|
||||
width: 4px;
|
||||
height: 4px;
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
border-radius: 50%;
|
||||
animation: float 15s infinite linear;
|
||||
}
|
||||
|
||||
.particle:nth-child(1) {
|
||||
left: 10%;
|
||||
animation-delay: 0s;
|
||||
animation-duration: 12s;
|
||||
}
|
||||
|
||||
.particle:nth-child(2) {
|
||||
left: 20%;
|
||||
animation-delay: 2s;
|
||||
animation-duration: 18s;
|
||||
}
|
||||
|
||||
.particle:nth-child(3) {
|
||||
left: 30%;
|
||||
animation-delay: 4s;
|
||||
animation-duration: 15s;
|
||||
}
|
||||
|
||||
.particle:nth-child(4) {
|
||||
left: 40%;
|
||||
animation-delay: 6s;
|
||||
animation-duration: 20s;
|
||||
}
|
||||
|
||||
.particle:nth-child(5) {
|
||||
left: 50%;
|
||||
animation-delay: 8s;
|
||||
animation-duration: 14s;
|
||||
}
|
||||
|
||||
.particle:nth-child(6) {
|
||||
left: 60%;
|
||||
animation-delay: 10s;
|
||||
animation-duration: 16s;
|
||||
}
|
||||
|
||||
.particle:nth-child(7) {
|
||||
left: 70%;
|
||||
animation-delay: 12s;
|
||||
animation-duration: 22s;
|
||||
}
|
||||
|
||||
.particle:nth-child(8) {
|
||||
left: 80%;
|
||||
animation-delay: 14s;
|
||||
animation-duration: 13s;
|
||||
}
|
||||
|
||||
.particle:nth-child(9) {
|
||||
left: 90%;
|
||||
animation-delay: 16s;
|
||||
animation-duration: 19s;
|
||||
}
|
||||
|
||||
.particle:nth-child(10) {
|
||||
left: 15%;
|
||||
animation-delay: 18s;
|
||||
animation-duration: 17s;
|
||||
}
|
||||
|
||||
@keyframes float {
|
||||
0% {
|
||||
transform: translateY(100vh) rotate(0deg);
|
||||
opacity: 0;
|
||||
}
|
||||
10% {
|
||||
opacity: 1;
|
||||
}
|
||||
90% {
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
transform: translateY(-100px) rotate(360deg);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* 网格背景效果 */
|
||||
.grid-background {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image:
|
||||
linear-gradient(rgba(255, 255, 255, 0.1) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(255, 255, 255, 0.1) 1px, transparent 1px);
|
||||
background-size: 50px 50px;
|
||||
z-index: -1;
|
||||
opacity: 0.3;
|
||||
animation: gridMove 30s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes gridMove {
|
||||
0% {
|
||||
transform: translate(0, 0);
|
||||
}
|
||||
100% {
|
||||
transform: translate(50px, 50px);
|
||||
}
|
||||
}
|
||||
|
||||
/* 光晕效果 */
|
||||
.glow-effect {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 300px;
|
||||
height: 300px;
|
||||
background: radial-gradient(circle, rgba(74, 144, 226, 0.2) 0%, transparent 70%);
|
||||
border-radius: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: -1;
|
||||
animation: pulse 4s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% {
|
||||
transform: translate(-50%, -50%) scale(1);
|
||||
opacity: 0.5;
|
||||
}
|
||||
50% {
|
||||
transform: translate(-50%, -50%) scale(1.2);
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
/* 响应式背景调整 */
|
||||
@media (max-width: 768px) {
|
||||
.grid-background {
|
||||
background-size: 30px 30px;
|
||||
}
|
||||
|
||||
.glow-effect {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
.particle {
|
||||
width: 3px;
|
||||
height: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.grid-background {
|
||||
background-size: 20px 20px;
|
||||
}
|
||||
|
||||
.glow-effect {
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
}
|
||||
|
||||
.particle {
|
||||
width: 2px;
|
||||
height: 2px;
|
||||
}
|
||||
}
|
||||
@@ -1,461 +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: #333;
|
||||
min-height: 100vh;
|
||||
overflow-x: hidden;
|
||||
/* 隐藏滚动条但保留滚动功能 */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
}
|
||||
|
||||
/* 隐藏 Webkit 浏览器的滚动条 */
|
||||
body::-webkit-scrollbar,
|
||||
html::-webkit-scrollbar,
|
||||
*::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* 全局隐藏滚动条但保留滚动功能 */
|
||||
html {
|
||||
scrollbar-width: none; /* Firefox */
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
}
|
||||
|
||||
/* 容器样式 */
|
||||
.container {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* 头部样式 */
|
||||
.header {
|
||||
text-align: center;
|
||||
padding: 3rem 2rem 2rem;
|
||||
background: linear-gradient(135deg, rgba(144, 238, 144, 0.15), rgba(152, 251, 152, 0.15));
|
||||
backdrop-filter: blur(10px);
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 2.5rem;
|
||||
font-weight: 700;
|
||||
background: linear-gradient(135deg, #228B22, #32CD32);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
margin-bottom: 0.5rem;
|
||||
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.header h1 i {
|
||||
margin-right: 0.5rem;
|
||||
background: linear-gradient(135deg, #228B22, #32CD32);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 1.1rem;
|
||||
color: #666;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
/* 主要内容区域 */
|
||||
.main-content {
|
||||
flex: 1;
|
||||
padding: 2rem;
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* 查询按钮区域 */
|
||||
.query-section {
|
||||
text-align: center;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.query-btn {
|
||||
background: linear-gradient(135deg, #228B22, #32CD32);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 1rem 2rem;
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
border-radius: 50px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 4px 15px rgba(34, 139, 34, 0.3);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
min-width: 200px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.query-btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 20px rgba(34, 139, 34, 0.4);
|
||||
background: linear-gradient(135deg, #1e7e1e, #2eb82e);
|
||||
}
|
||||
|
||||
.query-btn:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.query-btn:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
/* 加载动画 */
|
||||
.loading {
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border: 4px solid #f3f3f3;
|
||||
border-top: 4px solid #4a90e2;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin: 0 auto 1rem;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.loading p {
|
||||
color: #666;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
/* IP信息卡片 */
|
||||
.ip-info {
|
||||
animation: fadeInUp 0.6s ease;
|
||||
}
|
||||
|
||||
@keyframes fadeInUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.ip-card {
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
backdrop-filter: blur(10px);
|
||||
border-radius: 20px;
|
||||
padding: 2rem;
|
||||
margin-bottom: 2rem;
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.ip-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
padding-bottom: 1rem;
|
||||
border-bottom: 2px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.ip-header i {
|
||||
font-size: 1.5rem;
|
||||
color: #4a90e2;
|
||||
}
|
||||
|
||||
.ip-header h2 {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.ip-display {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 1rem;
|
||||
margin-bottom: 1.5rem;
|
||||
padding: 1.5rem;
|
||||
background: linear-gradient(135deg, rgba(74, 144, 226, 0.1), rgba(80, 200, 120, 0.1));
|
||||
border-radius: 15px;
|
||||
border: 2px solid rgba(74, 144, 226, 0.2);
|
||||
}
|
||||
|
||||
.ip-address {
|
||||
font-size: 2rem;
|
||||
font-weight: 700;
|
||||
font-family: 'Courier New', monospace;
|
||||
color: #2c3e50;
|
||||
background: linear-gradient(135deg, #4a90e2, #50c878);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.copy-btn {
|
||||
background: #4a90e2;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 0.5rem;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
font-size: 1rem;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.copy-btn:hover {
|
||||
background: #3a7bc8;
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.ip-details {
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.detail-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.75rem;
|
||||
background: rgba(248, 249, 250, 0.8);
|
||||
border-radius: 10px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.detail-item:hover {
|
||||
background: rgba(74, 144, 226, 0.1);
|
||||
transform: translateX(5px);
|
||||
}
|
||||
|
||||
.detail-item i {
|
||||
color: #4a90e2;
|
||||
width: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.detail-item .label {
|
||||
font-weight: 600;
|
||||
color: #555;
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
.detail-item .value {
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* IP地址说明区域 */
|
||||
.ip-explanation {
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
backdrop-filter: blur(10px);
|
||||
border-radius: 20px;
|
||||
padding: 2rem;
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.ip-explanation h3 {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 1rem;
|
||||
font-size: 1.3rem;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.ip-explanation h3 i {
|
||||
color: #4a90e2;
|
||||
}
|
||||
|
||||
.ip-explanation p {
|
||||
color: #666;
|
||||
line-height: 1.8;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.features {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.feature-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 1rem;
|
||||
padding: 1rem;
|
||||
background: rgba(248, 249, 250, 0.8);
|
||||
border-radius: 12px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.feature-item:hover {
|
||||
background: rgba(74, 144, 226, 0.1);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.feature-item i {
|
||||
color: #4a90e2;
|
||||
font-size: 1.5rem;
|
||||
margin-top: 0.2rem;
|
||||
}
|
||||
|
||||
.feature-item h4 {
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 0.3rem;
|
||||
}
|
||||
|
||||
.feature-item p {
|
||||
font-size: 0.9rem;
|
||||
color: #666;
|
||||
line-height: 1.5;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* 错误信息 */
|
||||
.error-message {
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
backdrop-filter: blur(10px);
|
||||
border-radius: 20px;
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
|
||||
border: 1px solid rgba(255, 99, 99, 0.3);
|
||||
animation: fadeInUp 0.6s ease;
|
||||
}
|
||||
|
||||
.error-message i {
|
||||
font-size: 3rem;
|
||||
color: #ff6b6b;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.error-message p {
|
||||
color: #666;
|
||||
font-size: 1.1rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.retry-btn {
|
||||
background: #ff6b6b;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 0.75rem 1.5rem;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
border-radius: 25px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.retry-btn:hover {
|
||||
background: #ff5252;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 15px rgba(255, 107, 107, 0.3);
|
||||
}
|
||||
|
||||
/* 页脚 */
|
||||
.footer {
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
background: rgba(248, 249, 250, 0.8);
|
||||
backdrop-filter: blur(10px);
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.2);
|
||||
color: #666;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.header {
|
||||
padding: 2rem 1rem 1.5rem;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.ip-card, .ip-explanation {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.ip-address {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.ip-display {
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.features {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.detail-item {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 0.3rem;
|
||||
}
|
||||
|
||||
.detail-item .label {
|
||||
min-width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.header h1 {
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
.query-btn {
|
||||
padding: 0.875rem 1.5rem;
|
||||
font-size: 1rem;
|
||||
min-width: 180px;
|
||||
}
|
||||
|
||||
.ip-address {
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
|
||||
.ip-card, .ip-explanation {
|
||||
padding: 1rem;
|
||||
}
|
||||
}
|
||||
@@ -1,131 +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>公网IP地址查询</title>
|
||||
<link rel="stylesheet" href="css/style.css">
|
||||
<link rel="stylesheet" href="css/background.css">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<!-- 头部 -->
|
||||
<header class="header">
|
||||
<h1><i class="fas fa-globe"></i> 公网IP地址查询</h1>
|
||||
<p class="subtitle">快速获取您的公网IP地址信息</p>
|
||||
</header>
|
||||
|
||||
<!-- 主要内容区域 -->
|
||||
<main class="main-content">
|
||||
<!-- 查询按钮区域 -->
|
||||
<div class="query-section">
|
||||
<button id="queryBtn" class="query-btn">
|
||||
<i class="fas fa-search"></i>
|
||||
<span>查询我的IP地址</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 加载动画 -->
|
||||
<div id="loading" class="loading" style="display: none;">
|
||||
<div class="spinner"></div>
|
||||
<p>正在获取IP地址信息...</p>
|
||||
</div>
|
||||
|
||||
<!-- IP信息展示区域 -->
|
||||
<div id="ip-info" class="ip-info" style="display: none;">
|
||||
<div class="ip-card">
|
||||
<div class="ip-header">
|
||||
<i class="fas fa-network-wired"></i>
|
||||
<h2>您的公网IP地址</h2>
|
||||
</div>
|
||||
<div class="ip-display">
|
||||
<span id="ip-address" class="ip-address">---.---.---.---</span>
|
||||
<button id="copyBtn" class="copy-btn" title="复制IP地址">
|
||||
<i class="fas fa-copy"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="ip-details">
|
||||
<div class="detail-item">
|
||||
<i class="fas fa-clock"></i>
|
||||
<span class="label">查询时间:</span>
|
||||
<span id="query-time" class="value">--</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<i class="fas fa-map-marker-alt"></i>
|
||||
<span class="label">位置信息:</span>
|
||||
<span id="location" class="value">--</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<i class="fas fa-building"></i>
|
||||
<span class="label">网络服务商:</span>
|
||||
<span id="isp" class="value">--</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<i class="fas fa-flag"></i>
|
||||
<span class="label">国家:</span>
|
||||
<span id="country" class="value">--</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<i class="fas fa-map"></i>
|
||||
<span class="label">地区:</span>
|
||||
<span id="region" class="value">--</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<i class="fas fa-city"></i>
|
||||
<span class="label">城市:</span>
|
||||
<span id="city" class="value">--</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<i class="fas fa-clock"></i>
|
||||
<span class="label">时区:</span>
|
||||
<span id="timezone" class="value">--</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- IP地址信息说明 -->
|
||||
<div class="ip-explanation">
|
||||
<h3><i class="fas fa-info-circle"></i> 什么是公网IP地址?</h3>
|
||||
<p>公网IP地址是您的设备在互联网上的唯一标识符,由您的网络服务提供商(ISP)分配。通过这个地址,互联网上的其他设备可以找到并与您的设备通信。</p>
|
||||
|
||||
<div class="features">
|
||||
<div class="feature-item">
|
||||
<i class="fas fa-shield-alt"></i>
|
||||
<div>
|
||||
<h4>隐私保护</h4>
|
||||
<p>了解您的IP地址有助于保护网络隐私</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="feature-item">
|
||||
<i class="fas fa-map-marker-alt"></i>
|
||||
<div>
|
||||
<h4>地理位置</h4>
|
||||
<p>IP地址可以大致确定您的地理位置</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="feature-item">
|
||||
<i class="fas fa-cogs"></i>
|
||||
<div>
|
||||
<h4>网络配置</h4>
|
||||
<p>用于网络故障排除和配置</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 错误信息 -->
|
||||
<div id="error-message" class="error-message" style="display: none;">
|
||||
<i class="fas fa-exclamation-triangle"></i>
|
||||
<p>获取IP地址失败,请检查网络连接或稍后重试</p>
|
||||
<button id="retryBtn" class="retry-btn">重试</button>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- 页脚 -->
|
||||
</div>
|
||||
|
||||
<script src="js/script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,343 +0,0 @@
|
||||
// 公网IP地址查询应用
|
||||
class IPQueryApp {
|
||||
constructor() {
|
||||
this.apiEndpoint = 'https://60s.api.shumengya.top/v2/ip';
|
||||
this.init();
|
||||
}
|
||||
|
||||
// 初始化应用
|
||||
init() {
|
||||
this.bindEvents();
|
||||
this.createParticles();
|
||||
this.createBackgroundElements();
|
||||
console.log('IP查询应用初始化完成');
|
||||
}
|
||||
|
||||
// 绑定事件
|
||||
bindEvents() {
|
||||
const queryBtn = document.getElementById('queryBtn');
|
||||
const retryBtn = document.getElementById('retryBtn');
|
||||
const copyBtn = document.getElementById('copyBtn');
|
||||
|
||||
if (queryBtn) {
|
||||
queryBtn.addEventListener('click', () => this.queryIP());
|
||||
}
|
||||
|
||||
if (retryBtn) {
|
||||
retryBtn.addEventListener('click', () => this.queryIP());
|
||||
}
|
||||
|
||||
if (copyBtn) {
|
||||
copyBtn.addEventListener('click', () => this.copyIP());
|
||||
}
|
||||
|
||||
// 页面加载完成后自动查询一次
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
setTimeout(() => this.queryIP(), 500);
|
||||
});
|
||||
}
|
||||
|
||||
// 创建浮动粒子
|
||||
createParticles() {
|
||||
const particlesContainer = document.createElement('div');
|
||||
particlesContainer.className = 'particles';
|
||||
document.body.appendChild(particlesContainer);
|
||||
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const particle = document.createElement('div');
|
||||
particle.className = 'particle';
|
||||
particlesContainer.appendChild(particle);
|
||||
}
|
||||
}
|
||||
|
||||
// 创建背景元素
|
||||
createBackgroundElements() {
|
||||
// 创建网格背景
|
||||
const gridBackground = document.createElement('div');
|
||||
gridBackground.className = 'grid-background';
|
||||
document.body.appendChild(gridBackground);
|
||||
|
||||
// 创建光晕效果
|
||||
const glowEffect = document.createElement('div');
|
||||
glowEffect.className = 'glow-effect';
|
||||
document.body.appendChild(glowEffect);
|
||||
}
|
||||
|
||||
// 显示加载状态
|
||||
showLoading() {
|
||||
const loading = document.getElementById('loading');
|
||||
const ipInfo = document.getElementById('ipInfo');
|
||||
const errorMessage = document.getElementById('errorMessage');
|
||||
const queryBtn = document.getElementById('queryBtn');
|
||||
|
||||
if (loading) loading.style.display = 'block';
|
||||
if (ipInfo) ipInfo.style.display = 'none';
|
||||
if (errorMessage) errorMessage.style.display = 'none';
|
||||
if (queryBtn) {
|
||||
queryBtn.disabled = true;
|
||||
queryBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> 查询中...';
|
||||
}
|
||||
}
|
||||
|
||||
// 隐藏加载状态
|
||||
hideLoading() {
|
||||
const loading = document.getElementById('loading');
|
||||
const queryBtn = document.getElementById('queryBtn');
|
||||
|
||||
if (loading) loading.style.display = 'none';
|
||||
if (queryBtn) {
|
||||
queryBtn.disabled = false;
|
||||
queryBtn.innerHTML = '<i class="fas fa-search"></i> 查询我的IP';
|
||||
}
|
||||
}
|
||||
|
||||
// 显示错误信息
|
||||
showError(message) {
|
||||
const errorMessage = document.getElementById('error-message');
|
||||
const ipInfo = document.getElementById('ip-info');
|
||||
|
||||
if (errorMessage) {
|
||||
errorMessage.style.display = 'block';
|
||||
const errorText = errorMessage.querySelector('p');
|
||||
if (errorText) errorText.textContent = message || '获取IP信息失败,请稍后重试';
|
||||
}
|
||||
if (ipInfo) ipInfo.style.display = 'none';
|
||||
|
||||
this.hideLoading();
|
||||
}
|
||||
|
||||
// 查询IP地址
|
||||
async queryIP() {
|
||||
try {
|
||||
this.showLoading();
|
||||
console.log('开始查询IP地址...');
|
||||
|
||||
const response = await fetch(this.apiEndpoint, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
console.log('API响应状态:', response.status);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP错误: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
console.log('API返回数据:', data);
|
||||
|
||||
if (data.code === 200 && data.data) {
|
||||
this.displayIPInfo(data.data);
|
||||
} else {
|
||||
throw new Error(data.message || '获取IP信息失败');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('查询IP失败:', error);
|
||||
this.showError(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// 显示IP信息
|
||||
displayIPInfo(data) {
|
||||
const ipInfo = document.getElementById('ip-info');
|
||||
const errorMessage = document.getElementById('error-message');
|
||||
|
||||
// 更新IP地址显示
|
||||
const ipAddressElement = document.getElementById('ip-address');
|
||||
if (ipAddressElement && data.ip) {
|
||||
ipAddressElement.textContent = data.ip;
|
||||
}
|
||||
|
||||
// 更新查询时间
|
||||
const queryTimeElement = document.getElementById('query-time');
|
||||
if (queryTimeElement) {
|
||||
const now = new Date();
|
||||
queryTimeElement.textContent = now.toLocaleString('zh-CN');
|
||||
}
|
||||
|
||||
// 更新详细信息 - 只显示API提供的数据
|
||||
if (data.location) this.updateDetailItem('location', data.location);
|
||||
else this.hideDetailItem('location');
|
||||
|
||||
if (data.isp) this.updateDetailItem('isp', data.isp);
|
||||
else this.hideDetailItem('isp');
|
||||
|
||||
if (data.country) this.updateDetailItem('country', data.country);
|
||||
else this.hideDetailItem('country');
|
||||
|
||||
if (data.region) this.updateDetailItem('region', data.region);
|
||||
else this.hideDetailItem('region');
|
||||
|
||||
if (data.city) this.updateDetailItem('city', data.city);
|
||||
else this.hideDetailItem('city');
|
||||
|
||||
if (data.timezone) this.updateDetailItem('timezone', data.timezone);
|
||||
else this.hideDetailItem('timezone');
|
||||
|
||||
// 显示IP信息,隐藏错误信息
|
||||
if (ipInfo) ipInfo.style.display = 'block';
|
||||
if (errorMessage) errorMessage.style.display = 'none';
|
||||
|
||||
this.hideLoading();
|
||||
console.log('IP信息显示完成');
|
||||
}
|
||||
|
||||
// 更新详细信息项
|
||||
updateDetailItem(id, value) {
|
||||
const element = document.getElementById(id);
|
||||
if (element) {
|
||||
element.textContent = value;
|
||||
// 显示对应的详细信息行
|
||||
const detailRow = element.closest('.detail-item');
|
||||
if (detailRow) {
|
||||
detailRow.style.display = 'flex';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 隐藏详细信息项
|
||||
hideDetailItem(id) {
|
||||
const element = document.getElementById(id);
|
||||
if (element) {
|
||||
// 隐藏整个详细信息行
|
||||
const detailRow = element.closest('.detail-item');
|
||||
if (detailRow) {
|
||||
detailRow.style.display = 'none';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 复制IP地址
|
||||
async copyIP() {
|
||||
const ipAddressElement = document.getElementById('ip-address');
|
||||
const copyBtn = document.getElementById('copyBtn');
|
||||
|
||||
if (!ipAddressElement || !ipAddressElement.textContent) {
|
||||
this.showToast('没有可复制的IP地址', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await navigator.clipboard.writeText(ipAddressElement.textContent);
|
||||
|
||||
// 更新按钮状态
|
||||
if (copyBtn) {
|
||||
const originalHTML = copyBtn.innerHTML;
|
||||
copyBtn.innerHTML = '<i class="fas fa-check"></i>';
|
||||
copyBtn.style.background = '#50c878';
|
||||
|
||||
setTimeout(() => {
|
||||
copyBtn.innerHTML = originalHTML;
|
||||
copyBtn.style.background = '#4a90e2';
|
||||
}, 1500);
|
||||
}
|
||||
|
||||
this.showToast('IP地址已复制到剪贴板', 'success');
|
||||
console.log('IP地址已复制:', ipAddressElement.textContent);
|
||||
|
||||
} catch (error) {
|
||||
console.error('复制失败:', error);
|
||||
this.showToast('复制失败,请手动选择复制', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// 显示提示消息
|
||||
showToast(message, type = 'info') {
|
||||
// 移除已存在的toast
|
||||
const existingToast = document.querySelector('.toast');
|
||||
if (existingToast) {
|
||||
existingToast.remove();
|
||||
}
|
||||
|
||||
// 创建新的toast
|
||||
const toast = document.createElement('div');
|
||||
toast.className = `toast toast-${type}`;
|
||||
toast.innerHTML = `
|
||||
<i class="fas fa-${type === 'success' ? 'check-circle' : type === 'error' ? 'exclamation-circle' : 'info-circle'}"></i>
|
||||
<span>${message}</span>
|
||||
`;
|
||||
|
||||
// 添加toast样式
|
||||
toast.style.cssText = `
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
background: ${type === 'success' ? '#50c878' : type === 'error' ? '#ff6b6b' : '#4a90e2'};
|
||||
color: white;
|
||||
padding: 1rem 1.5rem;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
|
||||
z-index: 1000;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
font-weight: 500;
|
||||
animation: slideInRight 0.3s ease;
|
||||
max-width: 300px;
|
||||
`;
|
||||
|
||||
// 添加动画样式
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
@keyframes slideInRight {
|
||||
from {
|
||||
transform: translateX(100%);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: translateX(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
@keyframes slideOutRight {
|
||||
from {
|
||||
transform: translateX(0);
|
||||
opacity: 1;
|
||||
}
|
||||
to {
|
||||
transform: translateX(100%);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
|
||||
document.body.appendChild(toast);
|
||||
|
||||
// 3秒后自动移除
|
||||
setTimeout(() => {
|
||||
toast.style.animation = 'slideOutRight 0.3s ease';
|
||||
setTimeout(() => {
|
||||
if (toast.parentNode) {
|
||||
toast.remove();
|
||||
}
|
||||
if (style.parentNode) {
|
||||
style.remove();
|
||||
}
|
||||
}, 300);
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
// 格式化时间
|
||||
formatTime(timestamp) {
|
||||
const date = new Date(timestamp);
|
||||
return date.toLocaleString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化应用
|
||||
const app = new IPQueryApp();
|
||||
|
||||
// 导出到全局作用域(用于调试)
|
||||
window.IPQueryApp = app;
|
||||
@@ -1,3 +0,0 @@
|
||||
[
|
||||
"https://60s.api.shumengya.top/v2/ip"
|
||||
]
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"code": 200,
|
||||
"message": "获取成功。数据来自官方/权威源头,以确保稳定与实时。开源地址 https://github.com/vikiboss/60s,反馈群 595941841",
|
||||
"data": {
|
||||
"ip": "2401:b60:16:83::"
|
||||
}
|
||||
}
|
||||
|
||||
// 注意:此API只返回IP地址,不包含以下信息:
|
||||
// - location (位置信息)
|
||||
// - isp (网络服务商)
|
||||
// - country (国家)
|
||||
// - region (地区)
|
||||
// - city (城市)
|
||||
// - timezone (时区)
|
||||
//
|
||||
// 如需这些信息,需要使用其他API服务
|
||||
@@ -1,590 +0,0 @@
|
||||
/* 全局滚动条隐藏样式 */
|
||||
html, body {
|
||||
scrollbar-width: none; /* Firefox */
|
||||
-ms-overflow-style: none; /* IE/Edge */
|
||||
}
|
||||
|
||||
html::-webkit-scrollbar,
|
||||
body::-webkit-scrollbar,
|
||||
*::-webkit-scrollbar {
|
||||
display: none; /* Webkit浏览器 */
|
||||
}
|
||||
|
||||
/* 农历主题背景样式 - 淡黄绿色到淡绿色清新渐变 */
|
||||
body {
|
||||
background: linear-gradient(135deg,
|
||||
#f0f8e8 0%, /* 淡黄绿色 */
|
||||
#e8f5e8 20%, /* 浅绿色 */
|
||||
#d4f4dd 40%, /* 淡绿色 */
|
||||
#c8f2d4 60%, /* 清新绿色 */
|
||||
#b8f0c8 80%, /* 柔和绿色 */
|
||||
#e8f5e8 100% /* 浅绿色 */
|
||||
);
|
||||
background-size: 400% 400%;
|
||||
animation: gentleShift 30s ease infinite;
|
||||
background-attachment: fixed;
|
||||
min-height: 100vh;
|
||||
position: relative;
|
||||
/* 隐藏滚动条但保留滚动功能 */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
-ms-overflow-style: none; /* IE/Edge */
|
||||
}
|
||||
|
||||
@keyframes gentleShift {
|
||||
0% { background-position: 0% 50%; }
|
||||
25% { background-position: 100% 50%; }
|
||||
50% { background-position: 100% 100%; }
|
||||
75% { background-position: 0% 100%; }
|
||||
100% { background-position: 0% 50%; }
|
||||
}
|
||||
|
||||
/* 动态颜色调节系统 - 绿色主题版本 */
|
||||
.adaptive-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background:
|
||||
radial-gradient(circle at 20% 30%, rgba(200, 242, 212, 0.3) 0%, transparent 50%),
|
||||
radial-gradient(circle at 80% 70%, rgba(184, 240, 200, 0.25) 0%, transparent 50%),
|
||||
linear-gradient(45deg, rgba(232, 245, 232, 0.2) 0%, rgba(212, 244, 221, 0.3) 100%);
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
animation: adaptiveShift 60s ease infinite;
|
||||
}
|
||||
|
||||
@keyframes adaptiveShift {
|
||||
0% {
|
||||
background:
|
||||
radial-gradient(circle at 20% 30%, rgba(232, 245, 232, 0.1) 0%, transparent 50%),
|
||||
radial-gradient(circle at 80% 70%, rgba(212, 244, 221, 0.15) 0%, transparent 50%),
|
||||
linear-gradient(45deg, rgba(240, 248, 232, 0.1) 0%, rgba(232, 245, 232, 0.2) 100%);
|
||||
}
|
||||
25% {
|
||||
background:
|
||||
radial-gradient(circle at 70% 20%, rgba(200, 242, 212, 0.2) 0%, transparent 50%),
|
||||
radial-gradient(circle at 30% 80%, rgba(184, 240, 200, 0.1) 0%, transparent 50%),
|
||||
linear-gradient(135deg, rgba(212, 244, 221, 0.15) 0%, rgba(200, 242, 212, 0.25) 100%);
|
||||
}
|
||||
50% {
|
||||
background:
|
||||
radial-gradient(circle at 50% 50%, rgba(220, 246, 228, 0.15) 0%, transparent 50%),
|
||||
radial-gradient(circle at 10% 90%, rgba(232, 245, 232, 0.12) 0%, transparent 50%),
|
||||
linear-gradient(225deg, rgba(240, 248, 232, 0.12) 0%, rgba(212, 244, 221, 0.22) 100%);
|
||||
}
|
||||
75% {
|
||||
background:
|
||||
radial-gradient(circle at 90% 60%, rgba(184, 240, 200, 0.18) 0%, transparent 50%),
|
||||
radial-gradient(circle at 40% 10%, rgba(240, 248, 232, 0.08) 0%, transparent 50%),
|
||||
linear-gradient(315deg, rgba(232, 245, 232, 0.1) 0%, rgba(200, 242, 212, 0.2) 100%);
|
||||
}
|
||||
100% {
|
||||
background:
|
||||
radial-gradient(circle at 20% 30%, rgba(232, 245, 232, 0.1) 0%, transparent 50%),
|
||||
radial-gradient(circle at 80% 70%, rgba(212, 244, 221, 0.15) 0%, transparent 50%),
|
||||
linear-gradient(45deg, rgba(240, 248, 232, 0.1) 0%, rgba(232, 245, 232, 0.2) 100%);
|
||||
}
|
||||
}
|
||||
|
||||
/* 高清稻穗贴图层 */
|
||||
body::before {
|
||||
content: '';
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image:
|
||||
/* 主稻穗束 - 高清细节 */
|
||||
radial-gradient(ellipse 1.5px 12px at 50% 45%, #DAA520 0%, #B8860B 30%, transparent 80%),
|
||||
radial-gradient(ellipse 1px 10px at 48% 50%, #FFD700 0%, #DAA520 40%, transparent 75%),
|
||||
radial-gradient(ellipse 1.2px 11px at 52% 48%, #FFCC00 0%, #B8860B 35%, transparent 78%),
|
||||
radial-gradient(ellipse 0.8px 9px at 49% 52%, #F4A460 0%, #DAA520 45%, transparent 70%),
|
||||
radial-gradient(ellipse 1.3px 13px at 51% 46%, #DEB887 0%, #B8860B 38%, transparent 82%),
|
||||
|
||||
/* 次级稻穗 */
|
||||
radial-gradient(ellipse 1px 8px at 30% 35%, #FFD700 0%, #DAA520 50%, transparent 75%),
|
||||
radial-gradient(ellipse 0.9px 7px at 32% 38%, #FFCC00 0%, #B8860B 45%, transparent 70%),
|
||||
radial-gradient(ellipse 1.1px 9px at 28% 36%, #DEB887 0%, #DAA520 40%, transparent 78%),
|
||||
|
||||
radial-gradient(ellipse 1px 8px at 70% 65%, #FFD700 0%, #DAA520 50%, transparent 75%),
|
||||
radial-gradient(ellipse 0.8px 7px at 72% 68%, #F4A460 0%, #B8860B 45%, transparent 70%),
|
||||
radial-gradient(ellipse 1.2px 9px at 68% 66%, #FFCC00 0%, #DAA520 40%, transparent 78%),
|
||||
|
||||
/* 散落稻粒 */
|
||||
radial-gradient(ellipse 0.5px 4px at 20% 80%, #FFD700 0%, transparent 60%),
|
||||
radial-gradient(ellipse 0.6px 5px at 80% 20%, #FFCC00 0%, transparent 65%),
|
||||
radial-gradient(ellipse 0.4px 3px at 15% 25%, #DEB887 0%, transparent 55%),
|
||||
radial-gradient(ellipse 0.7px 6px at 85% 75%, #DAA520 0%, transparent 70%),
|
||||
|
||||
/* 稻穗茎秆 - 更细致 */
|
||||
linear-gradient(88deg, transparent 49%, #9ACD32 49.5%, #8FBC8F 50%, #9ACD32 50.5%, transparent 51%),
|
||||
linear-gradient(92deg, transparent 49%, #8FBC8F 49.5%, #228B22 50%, #8FBC8F 50.5%, transparent 51%),
|
||||
linear-gradient(85deg, transparent 49%, #32CD32 49.5%, #9ACD32 50%, #32CD32 50.5%, transparent 51%);
|
||||
|
||||
background-size:
|
||||
25px 25px, 24px 24px, 26px 26px, 23px 23px, 27px 27px,
|
||||
20px 20px, 19px 19px, 21px 21px,
|
||||
22px 22px, 18px 18px, 23px 23px,
|
||||
15px 15px, 16px 16px, 14px 14px, 17px 17px,
|
||||
80px 80px, 85px 85px, 75px 75px;
|
||||
|
||||
background-position:
|
||||
0 0, 12px 12px, 6px 18px, 18px 6px, 3px 21px,
|
||||
40px 40px, 52px 48px, 35px 55px,
|
||||
120px 120px, 135px 115px, 110px 130px,
|
||||
200px 200px, 220px 180px, 180px 220px, 240px 160px,
|
||||
0 0, 40px 40px, 20px 60px;
|
||||
|
||||
opacity: 0.25;
|
||||
pointer-events: none;
|
||||
z-index: -2;
|
||||
animation: wheatSway 20s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes wheatSway {
|
||||
0%, 100% {
|
||||
transform: translateX(0) rotate(0deg);
|
||||
}
|
||||
25% {
|
||||
transform: translateX(5px) rotate(0.5deg);
|
||||
}
|
||||
50% {
|
||||
transform: translateX(-3px) rotate(-0.3deg);
|
||||
}
|
||||
75% {
|
||||
transform: translateX(2px) rotate(0.2deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* 大型稻穗背景层 */
|
||||
body::after {
|
||||
content: '';
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image:
|
||||
/* 主稻穗茎秆 - 右侧大型 */
|
||||
linear-gradient(85deg, transparent 45%, #9ACD32 47%, #8FBC8F 48%, #228B22 49%, #8FBC8F 50%, #9ACD32 51%, transparent 53%),
|
||||
linear-gradient(87deg, transparent 46%, #32CD32 47.5%, #9ACD32 48.5%, #8FBC8F 49.5%, #9ACD32 50.5%, #32CD32 51.5%, transparent 54%),
|
||||
|
||||
/* 主稻穗穗头 - 大型椭圆稻粒群 */
|
||||
radial-gradient(ellipse 8px 25px at 75% 15%, #FFD700 0%, #DAA520 30%, #B8860B 60%, transparent 85%),
|
||||
radial-gradient(ellipse 7px 23px at 77% 18%, #FFCC00 0%, #DAA520 35%, transparent 80%),
|
||||
radial-gradient(ellipse 9px 27px at 73% 12%, #F4A460 0%, #B8860B 40%, transparent 88%),
|
||||
radial-gradient(ellipse 6px 22px at 79% 20%, #DEB887 0%, #DAA520 45%, transparent 75%),
|
||||
radial-gradient(ellipse 8px 24px at 75% 16%, #FFD700 0%, #B8860B 38%, transparent 82%),
|
||||
|
||||
/* 稻穗分支 */
|
||||
radial-gradient(ellipse 5px 18px at 72% 25%, #FFCC00 0%, #DAA520 50%, transparent 75%),
|
||||
radial-gradient(ellipse 4px 16px at 78% 28%, #F4A460 0%, #B8860B 45%, transparent 70%),
|
||||
radial-gradient(ellipse 6px 20px at 70% 22%, #DEB887 0%, #DAA520 40%, transparent 78%),
|
||||
radial-gradient(ellipse 5px 17px at 80% 30%, #FFD700 0%, #B8860B 42%, transparent 76%),
|
||||
|
||||
/* 左侧稻穗茎秆 */
|
||||
linear-gradient(95deg, transparent 15%, #9ACD32 17%, #8FBC8F 18%, #228B22 19%, #8FBC8F 20%, #9ACD32 21%, transparent 23%),
|
||||
|
||||
/* 左侧稻穗穗头 */
|
||||
radial-gradient(ellipse 6px 20px at 25% 25%, #FFD700 0%, #DAA520 30%, transparent 80%),
|
||||
radial-gradient(ellipse 5px 18px at 27% 28%, #FFCC00 0%, #B8860B 35%, transparent 75%),
|
||||
radial-gradient(ellipse 7px 22px at 23% 22%, #F4A460 0%, #DAA520 40%, transparent 85%),
|
||||
|
||||
/* 麦田远景效果 */
|
||||
linear-gradient(180deg, transparent 70%, rgba(255, 215, 0, 0.1) 75%, rgba(218, 165, 32, 0.15) 85%, rgba(255, 215, 0, 0.2) 95%, rgba(255, 215, 0, 0.25) 100%),
|
||||
|
||||
/* 散落稻粒 */
|
||||
radial-gradient(ellipse 2px 8px at 60% 40%, #FFD700 0%, transparent 60%),
|
||||
radial-gradient(ellipse 1.5px 6px at 40% 60%, #FFCC00 0%, transparent 65%),
|
||||
radial-gradient(ellipse 2.5px 10px at 85% 50%, #DEB887 0%, transparent 70%),
|
||||
radial-gradient(ellipse 1.8px 7px at 15% 80%, #DAA520 0%, transparent 68%);
|
||||
|
||||
background-size:
|
||||
/* 主茎秆 */
|
||||
100vw 80vh, 100vw 82vh,
|
||||
/* 主穗头 */
|
||||
50vw 60vh, 48vw 58vh, 52vw 62vh, 46vw 56vh, 50vw 60vh,
|
||||
/* 分支 */
|
||||
40vw 50vh, 38vw 48vh, 42vw 52vh, 36vw 46vh,
|
||||
/* 左侧茎秆 */
|
||||
100vw 70vh,
|
||||
/* 左侧穗头 */
|
||||
35vw 45vh, 33vw 43vh, 37vw 47vh,
|
||||
/* 麦田远景 */
|
||||
100vw 100vh,
|
||||
/* 散落稻粒 */
|
||||
20vw 20vh, 25vw 25vh, 30vw 30vh, 22vw 22vh;
|
||||
|
||||
background-position:
|
||||
/* 主茎秆 */
|
||||
70% 20%, 72% 18%,
|
||||
/* 主穗头 */
|
||||
60% 0%, 62% 2%, 58% -2%, 64% 4%, 60% 1%,
|
||||
/* 分支 */
|
||||
65% 15%, 67% 17%, 63% 13%, 69% 19%,
|
||||
/* 左侧茎秆 */
|
||||
20% 30%,
|
||||
/* 左侧穗头 */
|
||||
15% 20%, 17% 22%, 13% 18%,
|
||||
/* 麦田远景 */
|
||||
0% 0%,
|
||||
/* 散落稻粒 */
|
||||
30% 50%, 50% 70%, 80% 40%, 10% 80%;
|
||||
|
||||
background-repeat: no-repeat;
|
||||
opacity: 0.4;
|
||||
pointer-events: none;
|
||||
z-index: -1;
|
||||
animation: wheatSway 25s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes spiralRotate {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* 流星效果容器 */
|
||||
.meteor-container {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
z-index: -1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 流星轨迹 */
|
||||
.meteor {
|
||||
position: absolute;
|
||||
width: 2px;
|
||||
height: 2px;
|
||||
background: radial-gradient(circle, #FFD700 0%, #FFA500 50%, transparent 100%);
|
||||
border-radius: 50%;
|
||||
box-shadow:
|
||||
0 0 10px #FFD700,
|
||||
0 0 20px #FFA500,
|
||||
0 0 30px #FF8C00;
|
||||
animation: meteorFall linear infinite;
|
||||
}
|
||||
|
||||
/* 流星尾迹 */
|
||||
.meteor::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100px;
|
||||
height: 1px;
|
||||
background: linear-gradient(90deg,
|
||||
#FFD700 0%,
|
||||
#FFA500 30%,
|
||||
#FF8C00 60%,
|
||||
transparent 100%);
|
||||
transform-origin: 0 50%;
|
||||
transform: rotate(-45deg);
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
@keyframes meteorFall {
|
||||
0% {
|
||||
transform: translateX(-100px) translateY(-100px);
|
||||
opacity: 0;
|
||||
}
|
||||
10% {
|
||||
opacity: 1;
|
||||
}
|
||||
90% {
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
transform: translateX(calc(100vw + 100px)) translateY(calc(100vh + 100px));
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* 多个流星的不同轨迹 */
|
||||
.meteor:nth-child(1) {
|
||||
top: 10%;
|
||||
left: -100px;
|
||||
animation-duration: 8s;
|
||||
animation-delay: 0s;
|
||||
}
|
||||
|
||||
.meteor:nth-child(2) {
|
||||
top: 20%;
|
||||
left: -100px;
|
||||
animation-duration: 12s;
|
||||
animation-delay: 2s;
|
||||
}
|
||||
|
||||
.meteor:nth-child(3) {
|
||||
top: 30%;
|
||||
left: -100px;
|
||||
animation-duration: 10s;
|
||||
animation-delay: 4s;
|
||||
}
|
||||
|
||||
.meteor:nth-child(4) {
|
||||
top: 50%;
|
||||
left: -100px;
|
||||
animation-duration: 15s;
|
||||
animation-delay: 6s;
|
||||
}
|
||||
|
||||
.meteor:nth-child(5) {
|
||||
top: 70%;
|
||||
left: -100px;
|
||||
animation-duration: 9s;
|
||||
animation-delay: 8s;
|
||||
}
|
||||
|
||||
.meteor:nth-child(6) {
|
||||
top: 80%;
|
||||
left: -100px;
|
||||
animation-duration: 11s;
|
||||
animation-delay: 10s;
|
||||
}
|
||||
|
||||
/* 金色粒子效果 */
|
||||
.golden-particles {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.particle {
|
||||
position: absolute;
|
||||
width: 3px;
|
||||
height: 3px;
|
||||
background: radial-gradient(circle, #FFD700 0%, #FFA500 70%, transparent 100%);
|
||||
border-radius: 50%;
|
||||
animation: particleFloat linear infinite;
|
||||
}
|
||||
|
||||
@keyframes particleFloat {
|
||||
0% {
|
||||
transform: translateY(100vh) rotate(0deg);
|
||||
opacity: 0;
|
||||
}
|
||||
10% {
|
||||
opacity: 1;
|
||||
}
|
||||
90% {
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
transform: translateY(-100px) rotate(360deg);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* 粒子的不同位置和动画时长 */
|
||||
.particle:nth-child(1) { left: 10%; animation-duration: 20s; animation-delay: 0s; }
|
||||
.particle:nth-child(2) { left: 20%; animation-duration: 25s; animation-delay: 2s; }
|
||||
.particle:nth-child(3) { left: 30%; animation-duration: 18s; animation-delay: 4s; }
|
||||
.particle:nth-child(4) { left: 40%; animation-duration: 22s; animation-delay: 6s; }
|
||||
.particle:nth-child(5) { left: 50%; animation-duration: 24s; animation-delay: 8s; }
|
||||
.particle:nth-child(6) { left: 60%; animation-duration: 19s; animation-delay: 10s; }
|
||||
.particle:nth-child(7) { left: 70%; animation-duration: 21s; animation-delay: 12s; }
|
||||
.particle:nth-child(8) { left: 80%; animation-duration: 23s; animation-delay: 14s; }
|
||||
.particle:nth-child(9) { left: 90%; animation-duration: 26s; animation-delay: 16s; }
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.meteor {
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
.meteor::before {
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
.particle {
|
||||
width: 2px;
|
||||
height: 2px;
|
||||
}
|
||||
|
||||
body::before {
|
||||
background-size:
|
||||
30px 30px, 25px 25px, 35px 35px, 28px 28px, 32px 32px,
|
||||
40px 40px, 45px 45px;
|
||||
}
|
||||
|
||||
body::after {
|
||||
background-size: 200px 200px, 150px 150px, 100px 100px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 麦穗飘舞特效 */
|
||||
.wheat-floating {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
z-index: 2;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 移动设备性能优化 */
|
||||
@media (max-width: 768px) {
|
||||
.wheat-floating {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.golden-particles {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.meteor-container {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.adaptive-overlay {
|
||||
animation: none;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
.wheat-particle {
|
||||
position: absolute;
|
||||
width: 8px;
|
||||
height: 20px;
|
||||
background: linear-gradient(180deg,
|
||||
#FFD700 0%,
|
||||
#DAA520 50%,
|
||||
#B8860B 100%
|
||||
);
|
||||
border-radius: 50% 50% 50% 50% / 60% 60% 40% 40%;
|
||||
opacity: 0.7;
|
||||
animation: wheatFloat 15s linear infinite;
|
||||
}
|
||||
|
||||
.wheat-particle::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -3px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 2px;
|
||||
height: 8px;
|
||||
background: #8B7355;
|
||||
border-radius: 1px;
|
||||
}
|
||||
|
||||
.wheat-particle::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
left: 1px;
|
||||
width: 2px;
|
||||
height: 4px;
|
||||
background: #FFEC8C;
|
||||
border-radius: 50%;
|
||||
box-shadow:
|
||||
3px 2px 0 #FFEC8C,
|
||||
1px 6px 0 #FFEC8C,
|
||||
4px 8px 0 #FFEC8C;
|
||||
}
|
||||
|
||||
@keyframes wheatFloat {
|
||||
0% {
|
||||
transform: translateY(-100vh) translateX(0) rotate(0deg);
|
||||
opacity: 0;
|
||||
}
|
||||
10% {
|
||||
opacity: 0.7;
|
||||
}
|
||||
90% {
|
||||
opacity: 0.7;
|
||||
}
|
||||
100% {
|
||||
transform: translateY(100vh) translateX(50px) rotate(360deg);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* 不同大小和速度的麦穗 */
|
||||
.wheat-particle:nth-child(1) {
|
||||
left: 10%;
|
||||
animation-duration: 12s;
|
||||
animation-delay: 0s;
|
||||
transform: scale(0.8);
|
||||
}
|
||||
|
||||
.wheat-particle:nth-child(2) {
|
||||
left: 25%;
|
||||
animation-duration: 18s;
|
||||
animation-delay: 2s;
|
||||
transform: scale(1.2);
|
||||
}
|
||||
|
||||
.wheat-particle:nth-child(3) {
|
||||
left: 40%;
|
||||
animation-duration: 15s;
|
||||
animation-delay: 4s;
|
||||
transform: scale(0.9);
|
||||
}
|
||||
|
||||
.wheat-particle:nth-child(4) {
|
||||
left: 60%;
|
||||
animation-duration: 20s;
|
||||
animation-delay: 1s;
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.wheat-particle:nth-child(5) {
|
||||
left: 75%;
|
||||
animation-duration: 14s;
|
||||
animation-delay: 3s;
|
||||
transform: scale(0.7);
|
||||
}
|
||||
|
||||
.wheat-particle:nth-child(6) {
|
||||
left: 90%;
|
||||
animation-duration: 16s;
|
||||
animation-delay: 5s;
|
||||
transform: scale(1.0);
|
||||
}
|
||||
|
||||
.wheat-particle:nth-child(7) {
|
||||
left: 5%;
|
||||
animation-duration: 22s;
|
||||
animation-delay: 6s;
|
||||
transform: scale(0.6);
|
||||
}
|
||||
|
||||
.wheat-particle:nth-child(8) {
|
||||
left: 35%;
|
||||
animation-duration: 13s;
|
||||
animation-delay: 2.5s;
|
||||
transform: scale(1.3);
|
||||
}
|
||||
|
||||
/* 减少动画偏好设置 */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
* {
|
||||
animation-duration: 0.01ms !important;
|
||||
animation-iteration-count: 1 !important;
|
||||
transition-duration: 0.01ms !important;
|
||||
}
|
||||
|
||||
.meteor,
|
||||
.particle {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@@ -1,577 +0,0 @@
|
||||
/* 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, #f0f8e8 0%, #e8f5e8 50%, #d4f4dd 100%);
|
||||
min-height: 100vh;
|
||||
color: #333;
|
||||
overflow-x: hidden;
|
||||
/* 隐藏滚动条但保留滚动功能 */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
}
|
||||
|
||||
/* 隐藏 Webkit 浏览器的滚动条 */
|
||||
body::-webkit-scrollbar,
|
||||
html::-webkit-scrollbar,
|
||||
*::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* 全局隐藏滚动条但保留滚动功能 */
|
||||
html {
|
||||
scrollbar-width: none; /* Firefox */
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
}
|
||||
|
||||
.container {
|
||||
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, #228B22, #32CD32);
|
||||
-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: #228B22;
|
||||
}
|
||||
|
||||
.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: #228B22;
|
||||
box-shadow: 0 0 0 3px rgba(34, 139, 34, 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, #228B22 0%, #32CD32 100%);
|
||||
color: white;
|
||||
box-shadow: 0 4px 15px rgba(34, 139, 34, 0.3);
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 25px rgba(34, 139, 34, 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, #228B22, #32CD32, #90EE90, #98FB98);
|
||||
}
|
||||
|
||||
.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: #228B22;
|
||||
}
|
||||
|
||||
.result-value .placeholder {
|
||||
color: #999;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.copy-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
color: #228B22;
|
||||
cursor: pointer;
|
||||
padding: 8px;
|
||||
border-radius: 6px;
|
||||
transition: all 0.3s ease;
|
||||
margin-left: auto;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.copy-btn:hover {
|
||||
background: rgba(34, 139, 34, 0.1);
|
||||
color: #1e7e1e;
|
||||
}
|
||||
|
||||
/* 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, #228B22, #32CD32);
|
||||
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);
|
||||
}
|
||||
@@ -1,212 +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>多功能哈希工具 - Hash Toolkit</title>
|
||||
<link rel="stylesheet" href="css/style.css">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<!-- Header Section -->
|
||||
<header class="header">
|
||||
<div class="header-content">
|
||||
<div class="logo">
|
||||
<i class="fas fa-fingerprint"></i>
|
||||
<h1>Hash Toolkit</h1>
|
||||
</div>
|
||||
<p class="subtitle">多功能哈希、编码与压缩工具</p>
|
||||
</div>
|
||||
<div class="header-decoration">
|
||||
<div class="floating-shapes">
|
||||
<div class="shape shape-1"></div>
|
||||
<div class="shape shape-2"></div>
|
||||
<div class="shape shape-3"></div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="main-content">
|
||||
<!-- Input Section -->
|
||||
<section class="input-section">
|
||||
<div class="input-card">
|
||||
<div class="card-header">
|
||||
<i class="fas fa-edit"></i>
|
||||
<h2>输入内容</h2>
|
||||
</div>
|
||||
<div class="input-wrapper">
|
||||
<textarea
|
||||
id="inputText"
|
||||
placeholder="请输入要处理的文本内容...\n支持中文、英文、特殊字符等"
|
||||
rows="6"
|
||||
></textarea>
|
||||
<div class="input-actions">
|
||||
<button id="clearBtn" class="btn btn-secondary">
|
||||
<i class="fas fa-trash"></i>
|
||||
清空
|
||||
</button>
|
||||
<button id="processBtn" class="btn btn-primary">
|
||||
<i class="fas fa-cogs"></i>
|
||||
开始处理
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Results Section -->
|
||||
<section class="results-section" id="resultsSection">
|
||||
<div class="results-grid">
|
||||
<!-- Hash Results -->
|
||||
<div class="result-card hash-card">
|
||||
<div class="card-header">
|
||||
<i class="fas fa-hashtag"></i>
|
||||
<h3>哈希算法</h3>
|
||||
</div>
|
||||
<div class="result-items">
|
||||
<div class="result-item">
|
||||
<label>MD5</label>
|
||||
<div class="result-value" id="md5Result">
|
||||
<span class="placeholder">等待处理...</span>
|
||||
<button class="copy-btn" data-target="md5Result">
|
||||
<i class="fas fa-copy"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="result-item">
|
||||
<label>SHA1</label>
|
||||
<div class="result-value" id="sha1Result">
|
||||
<span class="placeholder">等待处理...</span>
|
||||
<button class="copy-btn" data-target="sha1Result">
|
||||
<i class="fas fa-copy"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="result-item">
|
||||
<label>SHA256</label>
|
||||
<div class="result-value" id="sha256Result">
|
||||
<span class="placeholder">等待处理...</span>
|
||||
<button class="copy-btn" data-target="sha256Result">
|
||||
<i class="fas fa-copy"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="result-item">
|
||||
<label>SHA512</label>
|
||||
<div class="result-value" id="sha512Result">
|
||||
<span class="placeholder">等待处理...</span>
|
||||
<button class="copy-btn" data-target="sha512Result">
|
||||
<i class="fas fa-copy"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Encoding Results -->
|
||||
<div class="result-card encoding-card">
|
||||
<div class="card-header">
|
||||
<i class="fas fa-code"></i>
|
||||
<h3>编码转换</h3>
|
||||
</div>
|
||||
<div class="result-items">
|
||||
<div class="result-item">
|
||||
<label>Base64 编码</label>
|
||||
<div class="result-value" id="base64EncodeResult">
|
||||
<span class="placeholder">等待处理...</span>
|
||||
<button class="copy-btn" data-target="base64EncodeResult">
|
||||
<i class="fas fa-copy"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="result-item">
|
||||
<label>Base64 解码</label>
|
||||
<div class="result-value" id="base64DecodeResult">
|
||||
<span class="placeholder">等待处理...</span>
|
||||
<button class="copy-btn" data-target="base64DecodeResult">
|
||||
<i class="fas fa-copy"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="result-item">
|
||||
<label>URL 编码</label>
|
||||
<div class="result-value" id="urlEncodeResult">
|
||||
<span class="placeholder">等待处理...</span>
|
||||
<button class="copy-btn" data-target="urlEncodeResult">
|
||||
<i class="fas fa-copy"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="result-item">
|
||||
<label>URL 解码</label>
|
||||
<div class="result-value" id="urlDecodeResult">
|
||||
<span class="placeholder">等待处理...</span>
|
||||
<button class="copy-btn" data-target="urlDecodeResult">
|
||||
<i class="fas fa-copy"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Compression Results -->
|
||||
<div class="result-card compression-card">
|
||||
<div class="card-header">
|
||||
<i class="fas fa-compress-alt"></i>
|
||||
<h3>压缩算法</h3>
|
||||
</div>
|
||||
<div class="result-items">
|
||||
<div class="result-item">
|
||||
<label>Gzip 压缩</label>
|
||||
<div class="result-value" id="gzipCompressResult">
|
||||
<span class="placeholder">等待处理...</span>
|
||||
<button class="copy-btn" data-target="gzipCompressResult">
|
||||
<i class="fas fa-copy"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="result-item">
|
||||
<label>Deflate 压缩</label>
|
||||
<div class="result-value" id="deflateCompressResult">
|
||||
<span class="placeholder">等待处理...</span>
|
||||
<button class="copy-btn" data-target="deflateCompressResult">
|
||||
<i class="fas fa-copy"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="result-item">
|
||||
<label>Brotli 压缩</label>
|
||||
<div class="result-value" id="brotliCompressResult">
|
||||
<span class="placeholder">等待处理...</span>
|
||||
<button class="copy-btn" data-target="brotliCompressResult">
|
||||
<i class="fas fa-copy"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<!-- Loading Overlay -->
|
||||
<div class="loading-overlay" id="loadingOverlay">
|
||||
<div class="loading-spinner">
|
||||
<div class="spinner"></div>
|
||||
<p>正在处理中...</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Toast Notification -->
|
||||
<div class="toast" id="toast">
|
||||
<i class="fas fa-check-circle"></i>
|
||||
<span id="toastMessage">复制成功!</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="js/script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,394 +0,0 @@
|
||||
// API配置
|
||||
const API_BASE_URL = 'https://60s.api.shumengya.top/v2/hash';
|
||||
|
||||
// DOM元素
|
||||
const elements = {
|
||||
inputText: document.getElementById('inputText'),
|
||||
processBtn: document.getElementById('processBtn'),
|
||||
clearBtn: document.getElementById('clearBtn'),
|
||||
resultsSection: document.getElementById('resultsSection'),
|
||||
loadingOverlay: document.getElementById('loadingOverlay'),
|
||||
toast: document.getElementById('toast'),
|
||||
toastMessage: document.getElementById('toastMessage')
|
||||
};
|
||||
|
||||
// 结果元素映射
|
||||
const resultElements = {
|
||||
md5: document.getElementById('md5Result'),
|
||||
sha1: document.getElementById('sha1Result'),
|
||||
sha256: document.getElementById('sha256Result'),
|
||||
sha512: document.getElementById('sha512Result'),
|
||||
base64Encode: document.getElementById('base64EncodeResult'),
|
||||
base64Decode: document.getElementById('base64DecodeResult'),
|
||||
urlEncode: document.getElementById('urlEncodeResult'),
|
||||
urlDecode: document.getElementById('urlDecodeResult'),
|
||||
gzipCompress: document.getElementById('gzipCompressResult'),
|
||||
deflateCompress: document.getElementById('deflateCompressResult'),
|
||||
brotliCompress: document.getElementById('brotliCompressResult')
|
||||
};
|
||||
|
||||
// 初始化
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
initializeEventListeners();
|
||||
addInputAnimation();
|
||||
});
|
||||
|
||||
// 事件监听器初始化
|
||||
function initializeEventListeners() {
|
||||
// 处理按钮点击
|
||||
elements.processBtn.addEventListener('click', handleProcess);
|
||||
|
||||
// 清空按钮点击
|
||||
elements.clearBtn.addEventListener('click', handleClear);
|
||||
|
||||
// 输入框回车键
|
||||
elements.inputText.addEventListener('keydown', function(e) {
|
||||
if (e.ctrlKey && e.key === 'Enter') {
|
||||
handleProcess();
|
||||
}
|
||||
});
|
||||
|
||||
// 复制按钮事件委托
|
||||
document.addEventListener('click', function(e) {
|
||||
if (e.target.closest('.copy-btn')) {
|
||||
const copyBtn = e.target.closest('.copy-btn');
|
||||
const targetId = copyBtn.getAttribute('data-target');
|
||||
const targetElement = document.getElementById(targetId);
|
||||
const textContent = targetElement.textContent.trim();
|
||||
|
||||
if (textContent && textContent !== '等待处理...' && textContent !== '处理失败') {
|
||||
copyToClipboard(textContent);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 输入框实时验证
|
||||
elements.inputText.addEventListener('input', function() {
|
||||
const hasContent = this.value.trim().length > 0;
|
||||
elements.processBtn.disabled = !hasContent;
|
||||
|
||||
if (hasContent) {
|
||||
elements.processBtn.classList.remove('disabled');
|
||||
} else {
|
||||
elements.processBtn.classList.add('disabled');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 添加输入动画效果
|
||||
function addInputAnimation() {
|
||||
elements.inputText.addEventListener('focus', function() {
|
||||
this.parentElement.classList.add('focused');
|
||||
});
|
||||
|
||||
elements.inputText.addEventListener('blur', function() {
|
||||
this.parentElement.classList.remove('focused');
|
||||
});
|
||||
}
|
||||
|
||||
// 处理主要功能
|
||||
async function handleProcess() {
|
||||
const inputValue = elements.inputText.value.trim();
|
||||
|
||||
if (!inputValue) {
|
||||
showToast('请输入要处理的内容', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// 显示加载状态
|
||||
showLoading(true);
|
||||
resetResults();
|
||||
|
||||
try {
|
||||
// 调用API
|
||||
const response = await fetch(`${API_BASE_URL}?content=${encodeURIComponent(inputValue)}`);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.code === 200 && data.data) {
|
||||
displayResults(data.data);
|
||||
showResultsSection();
|
||||
showToast('处理完成!', 'success');
|
||||
} else {
|
||||
throw new Error(data.message || '处理失败');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('处理错误:', error);
|
||||
showToast(`处理失败: ${error.message}`, 'error');
|
||||
displayError();
|
||||
} finally {
|
||||
showLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
// 显示结果
|
||||
function displayResults(data) {
|
||||
try {
|
||||
// 哈希结果
|
||||
updateResultElement('md5', data.md5 || '不可用');
|
||||
|
||||
// SHA系列
|
||||
if (data.sha) {
|
||||
updateResultElement('sha1', data.sha.sha1 || '不可用');
|
||||
updateResultElement('sha256', data.sha.sha256 || '不可用');
|
||||
updateResultElement('sha512', data.sha.sha512 || '不可用');
|
||||
}
|
||||
|
||||
// Base64编码
|
||||
if (data.base64) {
|
||||
updateResultElement('base64Encode', data.base64.encoded || '不可用');
|
||||
// BASE64解码:只有当输入本身是BASE64格式时才显示解码结果
|
||||
let base64DecodeResult = data.base64.decoded;
|
||||
if (!base64DecodeResult) {
|
||||
// 检查输入是否为有效的BASE64格式
|
||||
const inputValue = elements.inputText.value.trim();
|
||||
const base64Regex = /^[A-Za-z0-9+/]*={0,2}$/;
|
||||
if (base64Regex.test(inputValue) && inputValue.length % 4 === 0) {
|
||||
try {
|
||||
base64DecodeResult = atob(inputValue);
|
||||
} catch (e) {
|
||||
base64DecodeResult = '解码失败';
|
||||
}
|
||||
} else {
|
||||
base64DecodeResult = '输入非BASE64格式';
|
||||
}
|
||||
}
|
||||
updateResultElement('base64Decode', base64DecodeResult || '不可用');
|
||||
}
|
||||
|
||||
// URL编码
|
||||
if (data.url) {
|
||||
updateResultElement('urlEncode', data.url.encoded || '不可用');
|
||||
updateResultElement('urlDecode', data.url.decoded || '不可用');
|
||||
}
|
||||
|
||||
// 压缩结果(仅显示压缩,不显示解压)
|
||||
if (data.gzip) {
|
||||
updateResultElement('gzipCompress', data.gzip.encoded || '不可用');
|
||||
}
|
||||
|
||||
if (data.deflate) {
|
||||
updateResultElement('deflateCompress', data.deflate.encoded || '不可用');
|
||||
}
|
||||
|
||||
if (data.brotli) {
|
||||
updateResultElement('brotliCompress', data.brotli.encoded || '不可用');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('显示结果时出错:', error);
|
||||
showToast('显示结果时出错', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// 更新单个结果元素
|
||||
function updateResultElement(key, value) {
|
||||
const element = resultElements[key];
|
||||
if (element) {
|
||||
const textSpan = element.querySelector('span') || element;
|
||||
textSpan.textContent = value;
|
||||
textSpan.classList.remove('placeholder');
|
||||
|
||||
// 添加动画效果
|
||||
element.classList.add('slide-in');
|
||||
setTimeout(() => {
|
||||
element.classList.remove('slide-in');
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
|
||||
// 重置结果
|
||||
function resetResults() {
|
||||
Object.values(resultElements).forEach(element => {
|
||||
if (element) {
|
||||
const textSpan = element.querySelector('span') || element;
|
||||
textSpan.textContent = '等待处理...';
|
||||
textSpan.classList.add('placeholder');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 显示错误状态
|
||||
function displayError() {
|
||||
Object.values(resultElements).forEach(element => {
|
||||
if (element) {
|
||||
const textSpan = element.querySelector('span') || element;
|
||||
textSpan.textContent = '处理失败';
|
||||
textSpan.classList.add('placeholder');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 显示结果区域
|
||||
function showResultsSection() {
|
||||
elements.resultsSection.classList.add('show');
|
||||
|
||||
// 平滑滚动到结果区域
|
||||
setTimeout(() => {
|
||||
elements.resultsSection.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'start'
|
||||
});
|
||||
}, 100);
|
||||
}
|
||||
|
||||
// 清空功能
|
||||
function handleClear() {
|
||||
elements.inputText.value = '';
|
||||
elements.inputText.focus();
|
||||
elements.resultsSection.classList.remove('show');
|
||||
resetResults();
|
||||
elements.processBtn.disabled = true;
|
||||
elements.processBtn.classList.add('disabled');
|
||||
|
||||
showToast('内容已清空', 'info');
|
||||
}
|
||||
|
||||
// 复制到剪贴板
|
||||
async function copyToClipboard(text) {
|
||||
try {
|
||||
if (navigator.clipboard && window.isSecureContext) {
|
||||
await navigator.clipboard.writeText(text);
|
||||
} else {
|
||||
// 降级方案
|
||||
const textArea = document.createElement('textarea');
|
||||
textArea.value = text;
|
||||
textArea.style.position = 'fixed';
|
||||
textArea.style.left = '-999999px';
|
||||
textArea.style.top = '-999999px';
|
||||
document.body.appendChild(textArea);
|
||||
textArea.focus();
|
||||
textArea.select();
|
||||
document.execCommand('copy');
|
||||
textArea.remove();
|
||||
}
|
||||
|
||||
showToast('复制成功!', 'success');
|
||||
} catch (error) {
|
||||
console.error('复制失败:', error);
|
||||
showToast('复制失败,请手动复制', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// 显示/隐藏加载状态
|
||||
function showLoading(show) {
|
||||
if (show) {
|
||||
elements.loadingOverlay.classList.add('show');
|
||||
elements.processBtn.disabled = true;
|
||||
elements.processBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> 处理中...';
|
||||
} else {
|
||||
elements.loadingOverlay.classList.remove('show');
|
||||
elements.processBtn.disabled = false;
|
||||
elements.processBtn.innerHTML = '<i class="fas fa-cogs"></i> 开始处理';
|
||||
}
|
||||
}
|
||||
|
||||
// 显示提示消息
|
||||
function showToast(message, type = 'success') {
|
||||
elements.toastMessage.textContent = message;
|
||||
|
||||
// 设置图标和样式
|
||||
const icon = elements.toast.querySelector('i');
|
||||
icon.className = getToastIcon(type);
|
||||
|
||||
elements.toast.className = `toast ${type}`;
|
||||
elements.toast.classList.add('show');
|
||||
|
||||
// 自动隐藏
|
||||
setTimeout(() => {
|
||||
elements.toast.classList.remove('show');
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
// 获取提示图标
|
||||
function getToastIcon(type) {
|
||||
const icons = {
|
||||
success: 'fas fa-check-circle',
|
||||
error: 'fas fa-exclamation-circle',
|
||||
info: 'fas fa-info-circle',
|
||||
warning: 'fas fa-exclamation-triangle'
|
||||
};
|
||||
return icons[type] || icons.success;
|
||||
}
|
||||
|
||||
// 工具函数:防抖
|
||||
function debounce(func, wait) {
|
||||
let timeout;
|
||||
return function executedFunction(...args) {
|
||||
const later = () => {
|
||||
clearTimeout(timeout);
|
||||
func(...args);
|
||||
};
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(later, wait);
|
||||
};
|
||||
}
|
||||
|
||||
// 工具函数:节流
|
||||
function throttle(func, limit) {
|
||||
let inThrottle;
|
||||
return function() {
|
||||
const args = arguments;
|
||||
const context = this;
|
||||
if (!inThrottle) {
|
||||
func.apply(context, args);
|
||||
inThrottle = true;
|
||||
setTimeout(() => inThrottle = false, limit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 添加键盘快捷键支持
|
||||
document.addEventListener('keydown', function(e) {
|
||||
// Ctrl+Enter 处理
|
||||
if (e.ctrlKey && e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
if (!elements.processBtn.disabled) {
|
||||
handleProcess();
|
||||
}
|
||||
}
|
||||
|
||||
// Escape 清空
|
||||
if (e.key === 'Escape') {
|
||||
handleClear();
|
||||
}
|
||||
});
|
||||
|
||||
// 页面可见性变化处理
|
||||
document.addEventListener('visibilitychange', function() {
|
||||
if (document.hidden) {
|
||||
// 页面隐藏时的处理
|
||||
console.log('页面已隐藏');
|
||||
} else {
|
||||
// 页面显示时的处理
|
||||
console.log('页面已显示');
|
||||
}
|
||||
});
|
||||
|
||||
// 错误处理
|
||||
window.addEventListener('error', function(e) {
|
||||
console.error('全局错误:', e.error);
|
||||
showToast('发生未知错误,请刷新页面重试', 'error');
|
||||
});
|
||||
|
||||
// 未处理的Promise拒绝
|
||||
window.addEventListener('unhandledrejection', function(e) {
|
||||
console.error('未处理的Promise拒绝:', e.reason);
|
||||
showToast('网络请求失败,请检查网络连接', 'error');
|
||||
});
|
||||
|
||||
// 导出函数供测试使用
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = {
|
||||
handleProcess,
|
||||
copyToClipboard,
|
||||
showToast,
|
||||
debounce,
|
||||
throttle
|
||||
};
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
{
|
||||
"code": 200,
|
||||
"message": "获取成功。数据来自官方/权威源头,以确保稳定与实时。开源地址 https://github.com/vikiboss/60s,反馈群 595941841",
|
||||
"data": {
|
||||
"source": "hello",
|
||||
"md5": "5d41402abc4b2a76b9719d911017c592",
|
||||
"sha": {
|
||||
"sha1": "aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d",
|
||||
"sha256": "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824",
|
||||
"sha512": "9b71d224bd62f3785d96d46ad3ea3d73319bfbc2890caadae2dff72519673ca72323c3d99ba5c11d7c7acc6e14b8c5da0c4663475c2e5c3adef46f73bcdec043"
|
||||
},
|
||||
"base64": {
|
||||
"encoded": "aGVsbG8=",
|
||||
"decoded": ""
|
||||
},
|
||||
"url": {
|
||||
"encoded": "hello",
|
||||
"decoded": "hello"
|
||||
},
|
||||
"gzip": {
|
||||
"encoded": "1f8b0800000000000003cb48cdc9c9070086a6103605000000",
|
||||
"decoded": ""
|
||||
},
|
||||
"deflate": {
|
||||
"encoded": "789ccb48cdc9c90700062c0215",
|
||||
"decoded": ""
|
||||
},
|
||||
"brotli": {
|
||||
"encoded": "0b028068656c6c6f03",
|
||||
"decoded": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
注意:实际API返回的字段名是 encoded/decoded,不是 encode/decode
|
||||
@@ -1,137 +0,0 @@
|
||||
/* 背景样式文件 */
|
||||
|
||||
/* 页面主背景 */
|
||||
body {
|
||||
background: linear-gradient(135deg, #e8f5e8 0%, #f0fdf4 25%, #dcfce7 50%, #f0fdf4 75%, #e8f5e8 100%);
|
||||
background-size: 400% 400%;
|
||||
animation: gradientShift 15s ease infinite;
|
||||
background-attachment: fixed;
|
||||
}
|
||||
|
||||
/* 背景动画 */
|
||||
@keyframes gradientShift {
|
||||
0% {
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
50% {
|
||||
background-position: 100% 50%;
|
||||
}
|
||||
100% {
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
}
|
||||
|
||||
/* 容器背景 */
|
||||
.container {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
backdrop-filter: blur(10px);
|
||||
border-radius: 20px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
/* 翻译框背景 */
|
||||
.translate-box {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
backdrop-filter: blur(20px);
|
||||
border: 1px solid rgba(116, 198, 157, 0.3);
|
||||
}
|
||||
|
||||
/* 输入框背景 */
|
||||
#input-text {
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
#input-text:focus {
|
||||
background: rgba(255, 255, 255, 1);
|
||||
}
|
||||
|
||||
/* 输出框背景 */
|
||||
.output-text {
|
||||
background: #f8fffe;
|
||||
}
|
||||
|
||||
/* 按钮背景 */
|
||||
.translate-btn {
|
||||
background: linear-gradient(135deg, #74c69d, #52b788);
|
||||
}
|
||||
|
||||
.translate-btn:hover {
|
||||
background: linear-gradient(135deg, #52b788, #40916c);
|
||||
}
|
||||
|
||||
.translate-btn:disabled {
|
||||
background: #b7e4c7;
|
||||
}
|
||||
|
||||
.swap-btn {
|
||||
background: #74c69d;
|
||||
}
|
||||
|
||||
.swap-btn:hover {
|
||||
background: #52b788;
|
||||
}
|
||||
|
||||
/* 语言选择器背景 */
|
||||
.lang-select {
|
||||
background: white;
|
||||
}
|
||||
|
||||
.lang-select:focus {
|
||||
background: rgba(255, 255, 255, 1);
|
||||
}
|
||||
|
||||
/* 发音信息背景 */
|
||||
.pronounce-item {
|
||||
background: rgba(116, 198, 157, 0.1);
|
||||
}
|
||||
|
||||
/* 清除和复制按钮背景 */
|
||||
.clear-btn:hover,
|
||||
.copy-btn:hover {
|
||||
background: rgba(116, 198, 157, 0.1);
|
||||
}
|
||||
|
||||
/* 提示消息背景 */
|
||||
.toast {
|
||||
background: #52b788;
|
||||
}
|
||||
|
||||
.toast.error {
|
||||
background: #e74c3c;
|
||||
}
|
||||
|
||||
/* 响应式背景调整 */
|
||||
@media (max-width: 767px) {
|
||||
body {
|
||||
background-size: 200% 200%;
|
||||
animation-duration: 10s;
|
||||
}
|
||||
|
||||
.container {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
backdrop-filter: blur(5px);
|
||||
}
|
||||
|
||||
.translate-box {
|
||||
background: rgba(255, 255, 255, 0.98);
|
||||
backdrop-filter: blur(15px);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
body {
|
||||
background-size: 150% 150%;
|
||||
animation-duration: 8s;
|
||||
}
|
||||
|
||||
.container {
|
||||
background: transparent;
|
||||
backdrop-filter: none;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.translate-box {
|
||||
background: rgba(255, 255, 255, 0.99);
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
}
|
||||
@@ -1,99 +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>在线翻译 - 支持109种语言</title>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<link rel="stylesheet" href="background.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<header class="header">
|
||||
<h1>在线机器翻译</h1>
|
||||
<p class="subtitle">支持109种语言互译</p>
|
||||
</header>
|
||||
|
||||
<main class="main-content">
|
||||
<div class="translate-box">
|
||||
<div class="language-selector">
|
||||
<div class="lang-group">
|
||||
<label for="from-lang">源语言</label>
|
||||
<select id="from-lang" class="lang-select">
|
||||
<option value="auto">自动检测</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<button class="swap-btn" id="swap-btn" title="交换语言">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M7 16l4-4-4-4"></path>
|
||||
<path d="M17 8l-4 4 4 4"></path>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<div class="lang-group">
|
||||
<label for="to-lang">目标语言</label>
|
||||
<select id="to-lang" class="lang-select">
|
||||
<option value="auto">自动选择</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-areas">
|
||||
<div class="input-section">
|
||||
<div class="textarea-header">
|
||||
<span class="detected-lang" id="detected-lang"></span>
|
||||
<button class="clear-btn" id="clear-btn" title="清空">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<line x1="18" y1="6" x2="6" y2="18"></line>
|
||||
<line x1="6" y1="6" x2="18" y2="18"></line>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<textarea
|
||||
id="input-text"
|
||||
placeholder="请输入要翻译的文本..."
|
||||
maxlength="5000"
|
||||
></textarea>
|
||||
<div class="char-count">
|
||||
<span id="char-count">0</span>/5000
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="output-section">
|
||||
<div class="textarea-header">
|
||||
<span class="target-lang" id="target-lang"></span>
|
||||
<button class="copy-btn" id="copy-btn" title="复制">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
|
||||
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div id="output-text" class="output-text">翻译结果将显示在这里...</div>
|
||||
<div class="pronounce-section" id="pronounce-section">
|
||||
<div class="pronounce-item" id="source-pronounce"></div>
|
||||
<div class="pronounce-item" id="target-pronounce"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="action-buttons">
|
||||
<button class="translate-btn" id="translate-btn">
|
||||
<span class="btn-text">翻译</span>
|
||||
<div class="loading-spinner" id="loading-spinner"></div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="footer">
|
||||
<p>数据来源于有道翻译,与其网页端同步</p>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<div class="toast" id="toast"></div>
|
||||
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,452 +0,0 @@
|
||||
// 全局变量
|
||||
let supportedLanguages = {};
|
||||
let isTranslating = false;
|
||||
|
||||
// DOM元素
|
||||
const elements = {
|
||||
fromLang: null,
|
||||
toLang: null,
|
||||
inputText: null,
|
||||
outputText: null,
|
||||
translateBtn: null,
|
||||
swapBtn: null,
|
||||
clearBtn: null,
|
||||
copyBtn: null,
|
||||
charCount: null,
|
||||
detectedLang: null,
|
||||
targetLang: null,
|
||||
pronounceSection: null
|
||||
};
|
||||
|
||||
// 初始化
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
initializeElements();
|
||||
loadSupportedLanguages();
|
||||
bindEvents();
|
||||
updateCharCount();
|
||||
});
|
||||
|
||||
// 初始化DOM元素
|
||||
function initializeElements() {
|
||||
elements.fromLang = document.getElementById('from-lang');
|
||||
elements.toLang = document.getElementById('to-lang');
|
||||
elements.inputText = document.getElementById('input-text');
|
||||
elements.outputText = document.getElementById('output-text');
|
||||
elements.translateBtn = document.getElementById('translate-btn');
|
||||
elements.swapBtn = document.getElementById('swap-btn');
|
||||
elements.clearBtn = document.getElementById('clear-btn');
|
||||
elements.copyBtn = document.getElementById('copy-btn');
|
||||
elements.charCount = document.getElementById('char-count');
|
||||
elements.detectedLang = document.getElementById('detected-lang');
|
||||
elements.targetLang = document.getElementById('target-lang');
|
||||
elements.pronounceSection = document.getElementById('pronounce-section');
|
||||
}
|
||||
|
||||
// 加载支持的语言列表
|
||||
async function loadSupportedLanguages() {
|
||||
try {
|
||||
const response = await fetch('https://60s.viki.moe/v2/fanyi/langs');
|
||||
const data = await response.json();
|
||||
|
||||
if (data.code === 200 && data.data && Array.isArray(data.data)) {
|
||||
// 转换数组格式为对象格式
|
||||
supportedLanguages = {};
|
||||
supportedLanguages['auto'] = '自动检测';
|
||||
data.data.forEach(lang => {
|
||||
supportedLanguages[lang.code] = lang.label;
|
||||
});
|
||||
populateLanguageSelectors();
|
||||
} else {
|
||||
throw new Error('获取语言列表失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载语言列表失败:', error);
|
||||
showToast('加载语言列表失败,请刷新页面重试', 'error');
|
||||
// 使用默认语言列表
|
||||
useDefaultLanguages();
|
||||
}
|
||||
}
|
||||
|
||||
// 使用默认语言列表(备用方案)
|
||||
function useDefaultLanguages() {
|
||||
supportedLanguages = {
|
||||
'auto': '自动检测',
|
||||
'zh-CHS': '中文',
|
||||
'en': '英语',
|
||||
'ja': '日语',
|
||||
'ko': '韩语',
|
||||
'fr': '法语',
|
||||
'de': '德语',
|
||||
'es': '西班牙语',
|
||||
'ru': '俄语',
|
||||
'th': '泰语',
|
||||
'ar': '阿拉伯语',
|
||||
'pt': '葡萄牙语',
|
||||
'it': '意大利语'
|
||||
};
|
||||
populateLanguageSelectors();
|
||||
}
|
||||
|
||||
// 填充语言选择器
|
||||
function populateLanguageSelectors() {
|
||||
const fromSelect = elements.fromLang;
|
||||
const toSelect = elements.toLang;
|
||||
|
||||
// 清空现有选项
|
||||
fromSelect.innerHTML = '';
|
||||
toSelect.innerHTML = '';
|
||||
|
||||
// 添加语言选项
|
||||
Object.entries(supportedLanguages).forEach(([code, name]) => {
|
||||
const fromOption = new Option(name, code);
|
||||
const toOption = new Option(name, code);
|
||||
|
||||
fromSelect.appendChild(fromOption);
|
||||
toSelect.appendChild(toOption);
|
||||
});
|
||||
|
||||
// 设置默认值
|
||||
fromSelect.value = 'auto';
|
||||
toSelect.value = 'en';
|
||||
|
||||
// 如果没有auto选项,则设置为中文
|
||||
if (!supportedLanguages['auto']) {
|
||||
fromSelect.value = 'zh-CHS';
|
||||
}
|
||||
}
|
||||
|
||||
// 绑定事件
|
||||
function bindEvents() {
|
||||
// 输入框事件
|
||||
elements.inputText.addEventListener('input', function() {
|
||||
updateCharCount();
|
||||
clearOutput();
|
||||
});
|
||||
|
||||
elements.inputText.addEventListener('keydown', function(e) {
|
||||
if (e.ctrlKey && e.key === 'Enter') {
|
||||
translateText();
|
||||
}
|
||||
});
|
||||
|
||||
// 按钮事件
|
||||
elements.translateBtn.addEventListener('click', translateText);
|
||||
elements.swapBtn.addEventListener('click', swapLanguages);
|
||||
elements.clearBtn.addEventListener('click', clearInput);
|
||||
elements.copyBtn.addEventListener('click', copyOutput);
|
||||
|
||||
// 语言选择器事件
|
||||
elements.fromLang.addEventListener('change', function() {
|
||||
clearOutput();
|
||||
updateLanguageLabels();
|
||||
});
|
||||
|
||||
elements.toLang.addEventListener('change', function() {
|
||||
clearOutput();
|
||||
updateLanguageLabels();
|
||||
});
|
||||
}
|
||||
|
||||
// 更新字符计数
|
||||
function updateCharCount() {
|
||||
const text = elements.inputText.value;
|
||||
const count = text.length;
|
||||
elements.charCount.textContent = `${count}/5000`;
|
||||
|
||||
if (count > 5000) {
|
||||
elements.charCount.style.color = '#e74c3c';
|
||||
} else {
|
||||
elements.charCount.style.color = '#74c69d';
|
||||
}
|
||||
}
|
||||
|
||||
// 更新语言标签
|
||||
function updateLanguageLabels() {
|
||||
const fromLang = elements.fromLang.value;
|
||||
const toLang = elements.toLang.value;
|
||||
|
||||
elements.detectedLang.textContent = supportedLanguages[fromLang] || '未知语言';
|
||||
elements.targetLang.textContent = supportedLanguages[toLang] || '未知语言';
|
||||
}
|
||||
|
||||
// 翻译文本
|
||||
async function translateText() {
|
||||
const text = elements.inputText.value.trim();
|
||||
|
||||
if (!text) {
|
||||
showToast('请输入要翻译的文本', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
if (text.length > 5000) {
|
||||
showToast('文本长度不能超过5000字符', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
if (isTranslating) {
|
||||
return;
|
||||
}
|
||||
|
||||
setTranslating(true);
|
||||
|
||||
try {
|
||||
const fromLang = elements.fromLang.value;
|
||||
const toLang = elements.toLang.value;
|
||||
|
||||
// 构建请求URL
|
||||
const params = new URLSearchParams({
|
||||
text: text,
|
||||
from: fromLang,
|
||||
to: toLang
|
||||
});
|
||||
|
||||
const response = await fetch(`https://60s.viki.moe/v2/fanyi?${params}`);
|
||||
const data = await response.json();
|
||||
|
||||
if (data.code === 200 && data.data) {
|
||||
displayTranslationResult(data.data);
|
||||
} else {
|
||||
throw new Error(data.msg || '翻译失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('翻译失败:', error);
|
||||
showToast('翻译失败: ' + error.message, 'error');
|
||||
elements.outputText.textContent = '翻译失败,请重试';
|
||||
} finally {
|
||||
setTranslating(false);
|
||||
}
|
||||
}
|
||||
|
||||
// 显示翻译结果
|
||||
function displayTranslationResult(data) {
|
||||
// 显示翻译结果
|
||||
const translation = data.target ? data.target.text : '';
|
||||
elements.outputText.textContent = translation;
|
||||
|
||||
// 更新检测到的语言
|
||||
if (data.source && data.source.type_desc) {
|
||||
elements.detectedLang.textContent = `检测: ${data.source.type_desc}`;
|
||||
}
|
||||
|
||||
// 显示发音信息
|
||||
displayPronunciation(data);
|
||||
|
||||
// 如果翻译结果为空
|
||||
if (!translation) {
|
||||
elements.outputText.textContent = '未获取到翻译结果';
|
||||
}
|
||||
}
|
||||
|
||||
// 显示发音信息
|
||||
function displayPronunciation(data) {
|
||||
const pronounceSection = elements.pronounceSection;
|
||||
if (!pronounceSection) {
|
||||
return;
|
||||
}
|
||||
pronounceSection.innerHTML = '';
|
||||
|
||||
// 原文发音
|
||||
if (data.source && data.source.pronounce) {
|
||||
const sourcePhoneticDiv = document.createElement('div');
|
||||
sourcePhoneticDiv.className = 'pronounce-item show';
|
||||
sourcePhoneticDiv.textContent = `原文发音: [${data.source.pronounce}]`;
|
||||
pronounceSection.appendChild(sourcePhoneticDiv);
|
||||
}
|
||||
|
||||
// 译文发音
|
||||
if (data.target && data.target.pronounce) {
|
||||
const targetPhoneticDiv = document.createElement('div');
|
||||
targetPhoneticDiv.className = 'pronounce-item show';
|
||||
targetPhoneticDiv.textContent = `译文发音: [${data.target.pronounce}]`;
|
||||
pronounceSection.appendChild(targetPhoneticDiv);
|
||||
}
|
||||
}
|
||||
|
||||
// 设置翻译状态
|
||||
function setTranslating(translating) {
|
||||
isTranslating = translating;
|
||||
elements.translateBtn.disabled = translating;
|
||||
|
||||
if (translating) {
|
||||
elements.translateBtn.classList.add('loading');
|
||||
} else {
|
||||
elements.translateBtn.classList.remove('loading');
|
||||
}
|
||||
}
|
||||
|
||||
// 交换语言
|
||||
function swapLanguages() {
|
||||
const fromValue = elements.fromLang.value;
|
||||
const toValue = elements.toLang.value;
|
||||
|
||||
// 不能交换自动检测
|
||||
if (fromValue === 'auto') {
|
||||
showToast('自动检测语言无法交换', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
elements.fromLang.value = toValue;
|
||||
elements.toLang.value = fromValue;
|
||||
|
||||
// 交换文本内容
|
||||
const inputText = elements.inputText.value;
|
||||
const outputText = elements.outputText.textContent;
|
||||
|
||||
if (outputText && outputText !== '翻译结果将在这里显示...' && outputText !== '翻译失败,请重试' && outputText !== '未获取到翻译结果') {
|
||||
elements.inputText.value = outputText;
|
||||
elements.outputText.textContent = inputText;
|
||||
}
|
||||
|
||||
updateCharCount();
|
||||
updateLanguageLabels();
|
||||
clearPronunciation();
|
||||
}
|
||||
|
||||
// 清空输入
|
||||
function clearInput() {
|
||||
elements.inputText.value = '';
|
||||
updateCharCount();
|
||||
clearOutput();
|
||||
}
|
||||
|
||||
// 清空输出
|
||||
function clearOutput() {
|
||||
elements.outputText.textContent = '翻译结果将在这里显示...';
|
||||
clearPronunciation();
|
||||
}
|
||||
|
||||
// 清空发音信息
|
||||
function clearPronunciation() {
|
||||
if (elements.pronounceSection) {
|
||||
elements.pronounceSection.innerHTML = '';
|
||||
}
|
||||
}
|
||||
|
||||
// 复制输出
|
||||
function copyOutput() {
|
||||
const text = elements.outputText.textContent;
|
||||
|
||||
if (!text || text === '翻译结果将在这里显示...' || text === '翻译失败,请重试' || text === '未获取到翻译结果') {
|
||||
showToast('没有可复制的内容', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// 使用现代API复制
|
||||
if (navigator.clipboard) {
|
||||
navigator.clipboard.writeText(text).then(() => {
|
||||
showToast('已复制到剪贴板');
|
||||
}).catch(() => {
|
||||
fallbackCopy(text);
|
||||
});
|
||||
} else {
|
||||
fallbackCopy(text);
|
||||
}
|
||||
}
|
||||
|
||||
// 备用复制方法
|
||||
function fallbackCopy(text) {
|
||||
const textArea = document.createElement('textarea');
|
||||
textArea.value = text;
|
||||
textArea.style.position = 'fixed';
|
||||
textArea.style.opacity = '0';
|
||||
document.body.appendChild(textArea);
|
||||
textArea.select();
|
||||
|
||||
try {
|
||||
document.execCommand('copy');
|
||||
showToast('已复制到剪贴板');
|
||||
} catch (err) {
|
||||
showToast('复制失败,请手动复制', 'error');
|
||||
}
|
||||
|
||||
document.body.removeChild(textArea);
|
||||
}
|
||||
|
||||
// 显示提示消息
|
||||
function showToast(message, type = 'success') {
|
||||
// 移除现有的toast
|
||||
const existingToast = document.querySelector('.toast');
|
||||
if (existingToast) {
|
||||
existingToast.remove();
|
||||
}
|
||||
|
||||
const toast = document.createElement('div');
|
||||
toast.className = `toast ${type}`;
|
||||
toast.textContent = message;
|
||||
|
||||
document.body.appendChild(toast);
|
||||
|
||||
// 显示toast
|
||||
setTimeout(() => {
|
||||
toast.classList.add('show');
|
||||
}, 100);
|
||||
|
||||
// 自动隐藏
|
||||
setTimeout(() => {
|
||||
toast.classList.remove('show');
|
||||
setTimeout(() => {
|
||||
if (toast.parentNode) {
|
||||
toast.parentNode.removeChild(toast);
|
||||
}
|
||||
}, 300);
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
// 工具函数:防抖
|
||||
function debounce(func, wait) {
|
||||
let timeout;
|
||||
return function executedFunction(...args) {
|
||||
const later = () => {
|
||||
clearTimeout(timeout);
|
||||
func(...args);
|
||||
};
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(later, wait);
|
||||
};
|
||||
}
|
||||
|
||||
// 添加键盘快捷键支持
|
||||
document.addEventListener('keydown', function(e) {
|
||||
// Ctrl+Enter 翻译
|
||||
if (e.ctrlKey && e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
translateText();
|
||||
}
|
||||
|
||||
// Ctrl+Shift+C 复制结果
|
||||
if (e.ctrlKey && e.shiftKey && e.key === 'C') {
|
||||
e.preventDefault();
|
||||
copyOutput();
|
||||
}
|
||||
|
||||
// Ctrl+Shift+X 清空输入
|
||||
if (e.ctrlKey && e.shiftKey && e.key === 'X') {
|
||||
e.preventDefault();
|
||||
clearInput();
|
||||
}
|
||||
|
||||
// Ctrl+Shift+S 交换语言
|
||||
if (e.ctrlKey && e.shiftKey && e.key === 'S') {
|
||||
e.preventDefault();
|
||||
swapLanguages();
|
||||
}
|
||||
});
|
||||
|
||||
// 页面可见性变化时的处理
|
||||
document.addEventListener('visibilitychange', function() {
|
||||
if (document.hidden) {
|
||||
// 页面隐藏时暂停翻译请求
|
||||
if (isTranslating) {
|
||||
setTranslating(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 错误处理
|
||||
window.addEventListener('error', function(e) {
|
||||
console.error('页面错误:', e.error);
|
||||
});
|
||||
|
||||
window.addEventListener('unhandledrejection', function(e) {
|
||||
console.error('未处理的Promise拒绝:', e.reason);
|
||||
});
|
||||
@@ -1,441 +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: #2d5a3d;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 头部样式 */
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 2.5rem;
|
||||
color: #1a4d2e;
|
||||
margin-bottom: 10px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 1.1rem;
|
||||
color: #4a7c59;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
/* 主要内容区域 */
|
||||
.main-content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.translate-box {
|
||||
width: 100%;
|
||||
max-width: 900px;
|
||||
border-radius: 20px;
|
||||
padding: 30px;
|
||||
box-shadow: 0 10px 30px rgba(26, 77, 46, 0.1);
|
||||
}
|
||||
|
||||
/* 语言选择器 */
|
||||
.language-selector {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
margin-bottom: 25px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.lang-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
flex: 1;
|
||||
max-width: 200px;
|
||||
}
|
||||
|
||||
.lang-group label {
|
||||
font-size: 0.9rem;
|
||||
color: #2d5a3d;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.lang-select {
|
||||
padding: 12px 16px;
|
||||
border: 2px solid #74c69d;
|
||||
border-radius: 12px;
|
||||
color: #2d5a3d;
|
||||
font-size: 1rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.lang-select:focus {
|
||||
outline: none;
|
||||
border-color: #52b788;
|
||||
box-shadow: 0 0 0 3px rgba(116, 198, 157, 0.2);
|
||||
}
|
||||
|
||||
.lang-select:hover {
|
||||
border-color: #52b788;
|
||||
}
|
||||
|
||||
.swap-btn {
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
width: 45px;
|
||||
height: 45px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
color: white;
|
||||
margin-top: 25px;
|
||||
}
|
||||
|
||||
.swap-btn:hover {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
/* 文本区域 */
|
||||
.text-areas {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 20px;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.input-section,
|
||||
.output-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.textarea-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
padding: 0 5px;
|
||||
}
|
||||
|
||||
.detected-lang,
|
||||
.target-lang {
|
||||
font-size: 0.9rem;
|
||||
color: #4a7c59;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.clear-btn,
|
||||
.copy-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
color: #74c69d;
|
||||
cursor: pointer;
|
||||
padding: 5px;
|
||||
border-radius: 6px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.clear-btn:hover,
|
||||
.copy-btn:hover {
|
||||
color: #52b788;
|
||||
}
|
||||
|
||||
#input-text {
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
padding: 16px;
|
||||
border: 2px solid #74c69d;
|
||||
border-radius: 12px;
|
||||
font-size: 1rem;
|
||||
color: #2d5a3d;
|
||||
resize: vertical;
|
||||
transition: all 0.3s ease;
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
#input-text:focus {
|
||||
outline: none;
|
||||
border-color: #52b788;
|
||||
box-shadow: 0 0 0 3px rgba(116, 198, 157, 0.2);
|
||||
}
|
||||
|
||||
#input-text::placeholder {
|
||||
color: #74c69d;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.output-text {
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
padding: 16px;
|
||||
border: 2px solid #b7e4c7;
|
||||
border-radius: 12px;
|
||||
font-size: 1rem;
|
||||
color: #2d5a3d;
|
||||
overflow-y: auto;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.char-count {
|
||||
text-align: right;
|
||||
font-size: 0.8rem;
|
||||
color: #74c69d;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.pronounce-section {
|
||||
margin-top: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.pronounce-item {
|
||||
font-size: 0.9rem;
|
||||
color: #4a7c59;
|
||||
font-style: italic;
|
||||
padding: 5px 10px;
|
||||
border-radius: 8px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.pronounce-item.show {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* 操作按钮 */
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.translate-btn {
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 15px 40px;
|
||||
border-radius: 25px;
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
min-width: 120px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.translate-btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 20px rgba(116, 198, 157, 0.3);
|
||||
}
|
||||
|
||||
.translate-btn:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.translate-btn:disabled {
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 2px solid rgba(255, 255, 255, 0.3);
|
||||
border-top: 2px solid white;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.translate-btn.loading .btn-text {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.translate-btn.loading .loading-spinner {
|
||||
display: block;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* 页脚 */
|
||||
.footer {
|
||||
text-align: center;
|
||||
margin-top: 30px;
|
||||
padding: 20px;
|
||||
color: #4a7c59;
|
||||
font-size: 0.9rem;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
/* 提示消息 */
|
||||
.toast {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
color: white;
|
||||
padding: 12px 20px;
|
||||
border-radius: 8px;
|
||||
font-size: 0.9rem;
|
||||
transform: translateX(100%);
|
||||
transition: transform 0.3s ease;
|
||||
z-index: 1000;
|
||||
box-shadow: 0 4px 12px rgba(82, 183, 136, 0.3);
|
||||
}
|
||||
|
||||
.toast.show {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.toast.error {
|
||||
box-shadow: 0 4px 12px rgba(231, 76, 60, 0.3);
|
||||
}
|
||||
|
||||
/* 平板适配 (768px - 1024px) */
|
||||
@media (max-width: 1024px) and (min-width: 768px) {
|
||||
.container {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 2.2rem;
|
||||
}
|
||||
|
||||
.translate-box {
|
||||
padding: 25px;
|
||||
}
|
||||
|
||||
.language-selector {
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.text-areas {
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
#input-text,
|
||||
.output-text {
|
||||
height: 180px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 手机端适配 (最大768px) */
|
||||
@media (max-width: 767px) {
|
||||
.container {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.translate-box {
|
||||
padding: 20px 15px;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.language-selector {
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.lang-group {
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
.swap-btn {
|
||||
align-self: center;
|
||||
margin-top: 0;
|
||||
order: 2;
|
||||
}
|
||||
|
||||
.text-areas {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
#input-text,
|
||||
.output-text {
|
||||
height: 150px;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.translate-btn {
|
||||
padding: 12px 30px;
|
||||
font-size: 1rem;
|
||||
width: 100%;
|
||||
max-width: 200px;
|
||||
}
|
||||
|
||||
.toast {
|
||||
right: 10px;
|
||||
left: 10px;
|
||||
transform: translateY(-100%);
|
||||
}
|
||||
|
||||
.toast.show {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* 超小屏幕适配 (最大480px) */
|
||||
@media (max-width: 480px) {
|
||||
.header h1 {
|
||||
font-size: 1.6rem;
|
||||
}
|
||||
|
||||
.translate-box {
|
||||
padding: 15px 10px;
|
||||
}
|
||||
|
||||
.lang-select {
|
||||
padding: 10px 12px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
#input-text,
|
||||
.output-text {
|
||||
height: 120px;
|
||||
padding: 12px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.translate-btn {
|
||||
padding: 10px 25px;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
}
|
||||
@@ -1,551 +0,0 @@
|
||||
{
|
||||
"code": 200,
|
||||
"message": "获取成功。数据来自官方/权威源头,以确保稳定与实时。开源地址 https://github.com/vikiboss/60s,反馈群 595941841",
|
||||
"data": [
|
||||
{
|
||||
"code": "sq",
|
||||
"label": "阿尔巴尼亚语",
|
||||
"alphabet": "A"
|
||||
},
|
||||
{
|
||||
"code": "ga",
|
||||
"label": "爱尔兰语",
|
||||
"alphabet": "A"
|
||||
},
|
||||
{
|
||||
"code": "et",
|
||||
"label": "爱沙尼亚语",
|
||||
"alphabet": "A"
|
||||
},
|
||||
{
|
||||
"code": "ar",
|
||||
"label": "阿拉伯语",
|
||||
"alphabet": "A"
|
||||
},
|
||||
{
|
||||
"code": "am",
|
||||
"label": "阿姆哈拉语",
|
||||
"alphabet": "A"
|
||||
},
|
||||
{
|
||||
"code": "az",
|
||||
"label": "阿塞拜疆语",
|
||||
"alphabet": "A"
|
||||
},
|
||||
{
|
||||
"code": "be",
|
||||
"label": "白俄罗斯语",
|
||||
"alphabet": "B"
|
||||
},
|
||||
{
|
||||
"code": "bg",
|
||||
"label": "保加利亚语",
|
||||
"alphabet": "B"
|
||||
},
|
||||
{
|
||||
"code": "eu",
|
||||
"label": "巴斯克语",
|
||||
"alphabet": "B"
|
||||
},
|
||||
{
|
||||
"code": "is",
|
||||
"label": "冰岛语",
|
||||
"alphabet": "B"
|
||||
},
|
||||
{
|
||||
"code": "pl",
|
||||
"label": "波兰语",
|
||||
"alphabet": "B"
|
||||
},
|
||||
{
|
||||
"code": "bs-Latn",
|
||||
"label": "波斯尼亚语(拉丁语)",
|
||||
"alphabet": "B"
|
||||
},
|
||||
{
|
||||
"code": "fa",
|
||||
"label": "波斯语",
|
||||
"alphabet": "B"
|
||||
},
|
||||
{
|
||||
"code": "da",
|
||||
"label": "丹麦语",
|
||||
"alphabet": "D"
|
||||
},
|
||||
{
|
||||
"code": "de",
|
||||
"label": "德语",
|
||||
"alphabet": "D"
|
||||
},
|
||||
{
|
||||
"code": "ru",
|
||||
"label": "俄语",
|
||||
"alphabet": "E"
|
||||
},
|
||||
{
|
||||
"code": "fr",
|
||||
"label": "法语",
|
||||
"alphabet": "F"
|
||||
},
|
||||
{
|
||||
"code": "tl",
|
||||
"label": "菲律宾语",
|
||||
"alphabet": "F"
|
||||
},
|
||||
{
|
||||
"code": "fi",
|
||||
"label": "芬兰语",
|
||||
"alphabet": "F"
|
||||
},
|
||||
{
|
||||
"code": "fy",
|
||||
"label": "弗里斯兰语",
|
||||
"alphabet": "F"
|
||||
},
|
||||
{
|
||||
"code": "km",
|
||||
"label": "高棉语",
|
||||
"alphabet": "G"
|
||||
},
|
||||
{
|
||||
"code": "ka",
|
||||
"label": "格鲁吉亚语",
|
||||
"alphabet": "G"
|
||||
},
|
||||
{
|
||||
"code": "gu",
|
||||
"label": "古吉拉特语",
|
||||
"alphabet": "G"
|
||||
},
|
||||
{
|
||||
"code": "ko",
|
||||
"label": "韩语",
|
||||
"alphabet": "H"
|
||||
},
|
||||
{
|
||||
"code": "ht",
|
||||
"label": "海地语",
|
||||
"alphabet": "H"
|
||||
},
|
||||
{
|
||||
"code": "ha",
|
||||
"label": "豪萨语",
|
||||
"alphabet": "H"
|
||||
},
|
||||
{
|
||||
"code": "kk",
|
||||
"label": "哈萨克语",
|
||||
"alphabet": "H"
|
||||
},
|
||||
{
|
||||
"code": "nl",
|
||||
"label": "荷兰语",
|
||||
"alphabet": "H"
|
||||
},
|
||||
{
|
||||
"code": "gl",
|
||||
"label": "加利西亚语",
|
||||
"alphabet": "J"
|
||||
},
|
||||
{
|
||||
"code": "ca",
|
||||
"label": "加泰罗尼亚语",
|
||||
"alphabet": "J"
|
||||
},
|
||||
{
|
||||
"code": "cs",
|
||||
"label": "捷克语",
|
||||
"alphabet": "J"
|
||||
},
|
||||
{
|
||||
"code": "ky",
|
||||
"label": "吉尔吉斯斯坦语",
|
||||
"alphabet": "J"
|
||||
},
|
||||
{
|
||||
"code": "kn",
|
||||
"label": "卡纳达语",
|
||||
"alphabet": "K"
|
||||
},
|
||||
{
|
||||
"code": "tlh",
|
||||
"label": "克林贡语",
|
||||
"alphabet": "K"
|
||||
},
|
||||
{
|
||||
"code": "hr",
|
||||
"label": "克罗地亚语",
|
||||
"alphabet": "K"
|
||||
},
|
||||
{
|
||||
"code": "otq",
|
||||
"label": "克洛塔罗乙巳语",
|
||||
"alphabet": "K"
|
||||
},
|
||||
{
|
||||
"code": "co",
|
||||
"label": "科西嘉语",
|
||||
"alphabet": "K"
|
||||
},
|
||||
{
|
||||
"code": "ku",
|
||||
"label": "库尔德语",
|
||||
"alphabet": "K"
|
||||
},
|
||||
{
|
||||
"code": "la",
|
||||
"label": "拉丁语",
|
||||
"alphabet": "L"
|
||||
},
|
||||
{
|
||||
"code": "lo",
|
||||
"label": "老挝语",
|
||||
"alphabet": "L"
|
||||
},
|
||||
{
|
||||
"code": "lv",
|
||||
"label": "拉脱维亚语",
|
||||
"alphabet": "L"
|
||||
},
|
||||
{
|
||||
"code": "lt",
|
||||
"label": "立陶宛语",
|
||||
"alphabet": "L"
|
||||
},
|
||||
{
|
||||
"code": "ro",
|
||||
"label": "罗马尼亚语",
|
||||
"alphabet": "L"
|
||||
},
|
||||
{
|
||||
"code": "lb",
|
||||
"label": "卢森堡语",
|
||||
"alphabet": "L"
|
||||
},
|
||||
{
|
||||
"code": "mg",
|
||||
"label": "马尔加什语",
|
||||
"alphabet": "M"
|
||||
},
|
||||
{
|
||||
"code": "mt",
|
||||
"label": "马耳他语",
|
||||
"alphabet": "M"
|
||||
},
|
||||
{
|
||||
"code": "mr",
|
||||
"label": "马拉地语",
|
||||
"alphabet": "M"
|
||||
},
|
||||
{
|
||||
"code": "ms",
|
||||
"label": "马来语",
|
||||
"alphabet": "M"
|
||||
},
|
||||
{
|
||||
"code": "ml",
|
||||
"label": "马拉雅拉姆语",
|
||||
"alphabet": "M"
|
||||
},
|
||||
{
|
||||
"code": "mi",
|
||||
"label": "毛利语",
|
||||
"alphabet": "M"
|
||||
},
|
||||
{
|
||||
"code": "mk",
|
||||
"label": "马其顿语",
|
||||
"alphabet": "M"
|
||||
},
|
||||
{
|
||||
"code": "mn",
|
||||
"label": "蒙古语",
|
||||
"alphabet": "M"
|
||||
},
|
||||
{
|
||||
"code": "bn",
|
||||
"label": "孟加拉语",
|
||||
"alphabet": "M"
|
||||
},
|
||||
{
|
||||
"code": "my",
|
||||
"label": "缅甸语",
|
||||
"alphabet": "M"
|
||||
},
|
||||
{
|
||||
"code": "mww",
|
||||
"label": "苗族昂山土语",
|
||||
"alphabet": "M"
|
||||
},
|
||||
{
|
||||
"code": "hmn",
|
||||
"label": "苗族语",
|
||||
"alphabet": "M"
|
||||
},
|
||||
{
|
||||
"code": "xh",
|
||||
"label": "南非科萨语",
|
||||
"alphabet": "N"
|
||||
},
|
||||
{
|
||||
"code": "zu",
|
||||
"label": "南非祖鲁语",
|
||||
"alphabet": "N"
|
||||
},
|
||||
{
|
||||
"code": "ne",
|
||||
"label": "尼泊尔语",
|
||||
"alphabet": "N"
|
||||
},
|
||||
{
|
||||
"code": "no",
|
||||
"label": "挪威语",
|
||||
"alphabet": "N"
|
||||
},
|
||||
{
|
||||
"code": "pa",
|
||||
"label": "旁遮普语",
|
||||
"alphabet": "P"
|
||||
},
|
||||
{
|
||||
"code": "ps",
|
||||
"label": "普什图语",
|
||||
"alphabet": "P"
|
||||
},
|
||||
{
|
||||
"code": "pt",
|
||||
"label": "葡萄牙语",
|
||||
"alphabet": "P"
|
||||
},
|
||||
{
|
||||
"code": "ny",
|
||||
"label": "齐切瓦语",
|
||||
"alphabet": "Q"
|
||||
},
|
||||
{
|
||||
"code": "ja",
|
||||
"label": "日语",
|
||||
"alphabet": "R"
|
||||
},
|
||||
{
|
||||
"code": "sv",
|
||||
"label": "瑞典语",
|
||||
"alphabet": "R"
|
||||
},
|
||||
{
|
||||
"code": "sr-Latn",
|
||||
"label": "塞尔维亚语(拉丁语)",
|
||||
"alphabet": "S"
|
||||
},
|
||||
{
|
||||
"code": "sr-Cyrl",
|
||||
"label": "塞尔维亚语(西里尔)",
|
||||
"alphabet": "S"
|
||||
},
|
||||
{
|
||||
"code": "st",
|
||||
"label": "塞索托语",
|
||||
"alphabet": "S"
|
||||
},
|
||||
{
|
||||
"code": "sm",
|
||||
"label": "萨摩亚语",
|
||||
"alphabet": "S"
|
||||
},
|
||||
{
|
||||
"code": "si",
|
||||
"label": "僧伽罗语",
|
||||
"alphabet": "S"
|
||||
},
|
||||
{
|
||||
"code": "eo",
|
||||
"label": "世界语",
|
||||
"alphabet": "S"
|
||||
},
|
||||
{
|
||||
"code": "sk",
|
||||
"label": "斯洛伐克语",
|
||||
"alphabet": "S"
|
||||
},
|
||||
{
|
||||
"code": "sl",
|
||||
"label": "斯洛语尼亚语",
|
||||
"alphabet": "S"
|
||||
},
|
||||
{
|
||||
"code": "sw",
|
||||
"label": "斯瓦希里语",
|
||||
"alphabet": "S"
|
||||
},
|
||||
{
|
||||
"code": "gd",
|
||||
"label": "苏格兰盖尔语",
|
||||
"alphabet": "S"
|
||||
},
|
||||
{
|
||||
"code": "so",
|
||||
"label": "索马里语",
|
||||
"alphabet": "S"
|
||||
},
|
||||
{
|
||||
"code": "ceb",
|
||||
"label": "宿务语",
|
||||
"alphabet": "S"
|
||||
},
|
||||
{
|
||||
"code": "te",
|
||||
"label": "泰卢固语",
|
||||
"alphabet": "T"
|
||||
},
|
||||
{
|
||||
"code": "ta",
|
||||
"label": "泰米尔语",
|
||||
"alphabet": "T"
|
||||
},
|
||||
{
|
||||
"code": "th",
|
||||
"label": "泰语",
|
||||
"alphabet": "T"
|
||||
},
|
||||
{
|
||||
"code": "tg",
|
||||
"label": "塔吉克语",
|
||||
"alphabet": "T"
|
||||
},
|
||||
{
|
||||
"code": "tr",
|
||||
"label": "土耳其语",
|
||||
"alphabet": "T"
|
||||
},
|
||||
{
|
||||
"code": "cy",
|
||||
"label": "威尔士语",
|
||||
"alphabet": "W"
|
||||
},
|
||||
{
|
||||
"code": "zh-lzh",
|
||||
"label": "文言文",
|
||||
"alphabet": "W"
|
||||
},
|
||||
{
|
||||
"code": "ur",
|
||||
"label": "乌尔都语",
|
||||
"alphabet": "W"
|
||||
},
|
||||
{
|
||||
"code": "uk",
|
||||
"label": "乌克兰语",
|
||||
"alphabet": "W"
|
||||
},
|
||||
{
|
||||
"code": "uz",
|
||||
"label": "乌兹别克语",
|
||||
"alphabet": "W"
|
||||
},
|
||||
{
|
||||
"code": "haw",
|
||||
"label": "夏威夷语",
|
||||
"alphabet": "X"
|
||||
},
|
||||
{
|
||||
"code": "es",
|
||||
"label": "西班牙语",
|
||||
"alphabet": "X"
|
||||
},
|
||||
{
|
||||
"code": "he",
|
||||
"label": "希伯来语",
|
||||
"alphabet": "X"
|
||||
},
|
||||
{
|
||||
"code": "el",
|
||||
"label": "希腊语",
|
||||
"alphabet": "X"
|
||||
},
|
||||
{
|
||||
"code": "sd",
|
||||
"label": "信德语",
|
||||
"alphabet": "X"
|
||||
},
|
||||
{
|
||||
"code": "hu",
|
||||
"label": "匈牙利语",
|
||||
"alphabet": "X"
|
||||
},
|
||||
{
|
||||
"code": "sn",
|
||||
"label": "修纳语",
|
||||
"alphabet": "X"
|
||||
},
|
||||
{
|
||||
"code": "en",
|
||||
"label": "英语",
|
||||
"alphabet": "Y"
|
||||
},
|
||||
{
|
||||
"code": "hy",
|
||||
"label": "亚美尼亚语",
|
||||
"alphabet": "Y"
|
||||
},
|
||||
{
|
||||
"code": "ig",
|
||||
"label": "伊博语",
|
||||
"alphabet": "Y"
|
||||
},
|
||||
{
|
||||
"code": "it",
|
||||
"label": "意大利语",
|
||||
"alphabet": "Y"
|
||||
},
|
||||
{
|
||||
"code": "yi",
|
||||
"label": "意第绪语",
|
||||
"alphabet": "Y"
|
||||
},
|
||||
{
|
||||
"code": "hi",
|
||||
"label": "印地语",
|
||||
"alphabet": "Y"
|
||||
},
|
||||
{
|
||||
"code": "id",
|
||||
"label": "印度尼西亚语",
|
||||
"alphabet": "Y"
|
||||
},
|
||||
{
|
||||
"code": "su",
|
||||
"label": "印尼巽他语",
|
||||
"alphabet": "Y"
|
||||
},
|
||||
{
|
||||
"code": "jw",
|
||||
"label": "印尼爪哇语",
|
||||
"alphabet": "Y"
|
||||
},
|
||||
{
|
||||
"code": "yua",
|
||||
"label": "尤卡坦玛雅语",
|
||||
"alphabet": "Y"
|
||||
},
|
||||
{
|
||||
"code": "yo",
|
||||
"label": "约鲁巴语",
|
||||
"alphabet": "Y"
|
||||
},
|
||||
{
|
||||
"code": "vi",
|
||||
"label": "越南语",
|
||||
"alphabet": "Y"
|
||||
},
|
||||
{
|
||||
"code": "zh-CHS",
|
||||
"label": "中文",
|
||||
"alphabet": "Z"
|
||||
},
|
||||
{
|
||||
"code": "zh-CHT",
|
||||
"label": "中文(繁体)",
|
||||
"alphabet": "Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
{"code":200,"message":"所有数据均来自官方,确保稳定与实时,用户群: 595941841,开源地址: https://github.com/vikiboss/60s","data":{"source":{"text":"こんにちは","type":"ja","type_desc":"日语","pronounce":"Konnitiha"},"target":{"text":"你好","type":"zh-CHS","type_desc":"中文","pronounce":"nĭhăo"}}}
|
||||
@@ -1,66 +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>
|
||||
|
||||
<div class="forecast-section">
|
||||
<h3>未来天气预报</h3>
|
||||
<div class="forecast-grid" id="forecastGrid">
|
||||
<!-- 预报数据将通过JavaScript动态生成 -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="update-time">
|
||||
<p>更新时间:<span id="updateTime"></span></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="error-message" id="errorMessage" style="display: none;">
|
||||
<p>获取天气信息失败,请稍后重试</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="js/script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,260 +0,0 @@
|
||||
// 天气查询应用
|
||||
class WeatherApp {
|
||||
constructor() {
|
||||
this.apiEndpoints = [
|
||||
"https://60s.api.shumengya.top/v2/weather/forecast",
|
||||
"https://60s-cf.viki.moe/v2/weather/forecast"
|
||||
];
|
||||
this.currentEndpointIndex = 0;
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
this.bindEvents();
|
||||
// 页面加载时自动查询北京天气
|
||||
this.searchWeather('北京');
|
||||
}
|
||||
|
||||
bindEvents() {
|
||||
const searchBtn = document.getElementById('searchBtn');
|
||||
const cityInput = document.getElementById('cityInput');
|
||||
|
||||
searchBtn.addEventListener('click', () => {
|
||||
const city = cityInput.value.trim();
|
||||
if (city) {
|
||||
this.searchWeather(city);
|
||||
} else {
|
||||
this.showError('请输入城市名称');
|
||||
}
|
||||
});
|
||||
|
||||
cityInput.addEventListener('keypress', (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
const city = cityInput.value.trim();
|
||||
if (city) {
|
||||
this.searchWeather(city);
|
||||
} else {
|
||||
this.showError('请输入城市名称');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 防止输入框为空时查询
|
||||
cityInput.addEventListener('input', () => {
|
||||
const searchBtn = document.getElementById('searchBtn');
|
||||
searchBtn.disabled = !cityInput.value.trim();
|
||||
});
|
||||
}
|
||||
|
||||
async searchWeather(city) {
|
||||
this.showLoading();
|
||||
|
||||
for (let i = 0; i < this.apiEndpoints.length; i++) {
|
||||
try {
|
||||
const endpoint = this.apiEndpoints[this.currentEndpointIndex];
|
||||
const response = await this.fetchWeatherData(endpoint, city);
|
||||
|
||||
if (response && response.code === 200) {
|
||||
this.displayWeatherData(response.data);
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(`API ${this.apiEndpoints[this.currentEndpointIndex]} 请求失败:`, error);
|
||||
}
|
||||
|
||||
// 切换到下一个API端点
|
||||
this.currentEndpointIndex = (this.currentEndpointIndex + 1) % this.apiEndpoints.length;
|
||||
}
|
||||
|
||||
// 所有API都失败了
|
||||
this.showError('获取天气信息失败,请检查网络连接或稍后重试');
|
||||
}
|
||||
|
||||
async fetchWeatherData(endpoint, city) {
|
||||
const url = `${endpoint}?query=${encodeURIComponent(city)}`;
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
timeout: 10000
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}`);
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
displayWeatherData(data) {
|
||||
const { location, daily_forecast, hourly_forecast } = data;
|
||||
|
||||
// 显示位置信息
|
||||
document.getElementById('locationName').textContent = location.name || '未知位置';
|
||||
document.getElementById('locationDetail').textContent =
|
||||
`${location.province || ''} ${location.city || ''} ${location.county || ''}`.trim();
|
||||
|
||||
// 使用第一天的预报数据作为当前天气(今天的天气)
|
||||
const todayWeather = daily_forecast && daily_forecast[0];
|
||||
|
||||
if (todayWeather) {
|
||||
// 显示当前天气(使用今天的最高温度)
|
||||
document.getElementById('temperature').textContent = todayWeather.max_temperature;
|
||||
document.getElementById('weatherCondition').textContent =
|
||||
`${todayWeather.day_condition} 转 ${todayWeather.night_condition}`;
|
||||
|
||||
// 体感温度(使用温度范围)
|
||||
document.getElementById('feelsLike').textContent =
|
||||
`温度范围 ${todayWeather.min_temperature}°C - ${todayWeather.max_temperature}°C`;
|
||||
} else {
|
||||
// 如果没有日预报数据,尝试使用小时预报数据
|
||||
const currentHour = hourly_forecast && hourly_forecast[0];
|
||||
if (currentHour) {
|
||||
document.getElementById('temperature').textContent = currentHour.temperature;
|
||||
document.getElementById('weatherCondition').textContent = currentHour.condition;
|
||||
document.getElementById('feelsLike').textContent =
|
||||
`风向: ${currentHour.wind_direction} ${currentHour.wind_power}`;
|
||||
}
|
||||
}
|
||||
|
||||
// 显示更新时间(使用当前时间)
|
||||
document.getElementById('updateTime').textContent =
|
||||
`${this.formatDate(new Date())} (基于预报数据)`;
|
||||
|
||||
// 显示天气预报
|
||||
this.displayForecast(daily_forecast || []);
|
||||
|
||||
this.showWeatherContainer();
|
||||
}
|
||||
|
||||
displayForecast(forecast) {
|
||||
const forecastGrid = document.getElementById('forecastGrid');
|
||||
forecastGrid.innerHTML = '';
|
||||
|
||||
if (!forecast || forecast.length === 0) {
|
||||
forecastGrid.innerHTML = '<div class="no-forecast">暂无预报数据</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
forecast.forEach((day, index) => {
|
||||
const forecastItem = document.createElement('div');
|
||||
forecastItem.className = 'forecast-item';
|
||||
|
||||
// 格式化日期显示
|
||||
const dateStr = day.date || '';
|
||||
const dateDesc = this.formatDateDesc(dateStr);
|
||||
|
||||
forecastItem.innerHTML = `
|
||||
<div class="forecast-date">${dateDesc}</div>
|
||||
<div class="forecast-weather">
|
||||
<div class="weather-day">${day.day_condition || '未知'}</div>
|
||||
<div class="weather-night">${day.night_condition || '未知'}</div>
|
||||
</div>
|
||||
<div class="forecast-temp">
|
||||
<span class="temp-high">${day.max_temperature || '--'}°</span>
|
||||
<span class="temp-low">${day.min_temperature || '--'}°</span>
|
||||
</div>
|
||||
<div class="forecast-wind">
|
||||
<div>${day.day_wind_direction || ''} ${day.day_wind_power || ''}</div>
|
||||
</div>
|
||||
<div class="forecast-air">空气质量: ${day.air_quality || '未知'}</div>
|
||||
`;
|
||||
|
||||
forecastGrid.appendChild(forecastItem);
|
||||
});
|
||||
}
|
||||
|
||||
// 华氏度转摄氏度
|
||||
fahrenheitToCelsius(fahrenheit) {
|
||||
const celsius = (fahrenheit - 32) * 5 / 9;
|
||||
return Math.round(celsius * 10) / 10; // 保留一位小数
|
||||
}
|
||||
|
||||
// 格式化时间
|
||||
formatDate(date) {
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
const hours = String(date.getHours()).padStart(2, '0');
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0');
|
||||
const seconds = String(date.getSeconds()).padStart(2, '0');
|
||||
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
||||
}
|
||||
|
||||
// 格式化日期描述
|
||||
formatDateDesc(dateStr) {
|
||||
if (!dateStr) return '未知日期';
|
||||
|
||||
try {
|
||||
const date = new Date(dateStr);
|
||||
const today = new Date();
|
||||
const tomorrow = new Date(today);
|
||||
tomorrow.setDate(today.getDate() + 1);
|
||||
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
|
||||
// 判断是今天、明天还是其他日期
|
||||
if (date.toDateString() === today.toDateString()) {
|
||||
return `今天 ${month}-${day}`;
|
||||
} else if (date.toDateString() === tomorrow.toDateString()) {
|
||||
return `明天 ${month}-${day}`;
|
||||
} else {
|
||||
const weekdays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
|
||||
const weekday = weekdays[date.getDay()];
|
||||
return `${weekday} ${month}-${day}`;
|
||||
}
|
||||
} catch (error) {
|
||||
return dateStr;
|
||||
}
|
||||
}
|
||||
|
||||
showLoading() {
|
||||
document.getElementById('loading').style.display = 'block';
|
||||
document.getElementById('weatherContainer').style.display = 'none';
|
||||
document.getElementById('errorMessage').style.display = 'none';
|
||||
}
|
||||
|
||||
showWeatherContainer() {
|
||||
document.getElementById('loading').style.display = 'none';
|
||||
document.getElementById('weatherContainer').style.display = 'block';
|
||||
document.getElementById('errorMessage').style.display = 'none';
|
||||
}
|
||||
|
||||
showError(message) {
|
||||
document.getElementById('loading').style.display = 'none';
|
||||
document.getElementById('weatherContainer').style.display = 'none';
|
||||
const errorElement = document.getElementById('errorMessage');
|
||||
errorElement.style.display = 'block';
|
||||
errorElement.querySelector('p').textContent = message;
|
||||
}
|
||||
}
|
||||
|
||||
// 页面加载完成后初始化应用
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
new WeatherApp();
|
||||
});
|
||||
|
||||
// 添加页面可见性检测,当页面重新可见时刷新数据
|
||||
document.addEventListener('visibilitychange', () => {
|
||||
if (!document.hidden) {
|
||||
const cityInput = document.getElementById('cityInput');
|
||||
const city = cityInput.value.trim() || '北京';
|
||||
// 延迟1秒刷新,避免频繁请求
|
||||
setTimeout(() => {
|
||||
if (window.weatherApp) {
|
||||
window.weatherApp.searchWeather(city);
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
});
|
||||
|
||||
// 将应用实例暴露到全局,方便调试和其他功能调用
|
||||
window.weatherApp = null;
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
window.weatherApp = new WeatherApp();
|
||||
});
|
||||
File diff suppressed because one or more lines are too long
@@ -1,202 +0,0 @@
|
||||
/* 背景样式文件 - 独立管理背景相关CSS */
|
||||
|
||||
/* 主体背景 */
|
||||
body {
|
||||
background: linear-gradient(135deg, #a8e6cf 0%, #dcedc8 50%, #f0f4c3 100%);
|
||||
background-attachment: fixed;
|
||||
background-size: cover;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* 背景装饰元素 */
|
||||
body::before {
|
||||
content: '';
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image:
|
||||
radial-gradient(circle at 20% 80%, rgba(120, 219, 226, 0.1) 0%, transparent 50%),
|
||||
radial-gradient(circle at 80% 20%, rgba(168, 230, 207, 0.1) 0%, transparent 50%),
|
||||
radial-gradient(circle at 40% 40%, rgba(220, 237, 200, 0.1) 0%, transparent 50%);
|
||||
pointer-events: none;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
/* 动态背景效果 */
|
||||
body::after {
|
||||
content: '';
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background:
|
||||
linear-gradient(45deg, transparent 30%, rgba(255, 255, 255, 0.05) 50%, transparent 70%),
|
||||
linear-gradient(-45deg, transparent 30%, rgba(168, 230, 207, 0.05) 50%, transparent 70%);
|
||||
background-size: 200px 200px;
|
||||
animation: backgroundMove 20s linear infinite;
|
||||
pointer-events: none;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
/* 背景动画 */
|
||||
@keyframes backgroundMove {
|
||||
0% {
|
||||
background-position: 0 0, 0 0;
|
||||
}
|
||||
100% {
|
||||
background-position: 200px 200px, -200px -200px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 容器背景 */
|
||||
.container {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
backdrop-filter: blur(5px);
|
||||
border-radius: 20px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
/* 卡片背景增强 */
|
||||
.weather-card {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
backdrop-filter: blur(15px);
|
||||
border: 1px solid rgba(168, 230, 207, 0.3);
|
||||
box-shadow:
|
||||
0 10px 30px rgba(0, 0, 0, 0.1),
|
||||
0 1px 8px rgba(168, 230, 207, 0.2),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
|
||||
/* 当前天气区域背景 */
|
||||
.current-weather {
|
||||
background: linear-gradient(135deg,
|
||||
rgba(168, 230, 207, 0.8) 0%,
|
||||
rgba(220, 237, 200, 0.8) 50%,
|
||||
rgba(240, 244, 195, 0.8) 100%);
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
box-shadow:
|
||||
0 4px 15px rgba(39, 174, 96, 0.1),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
|
||||
/* 详情项背景 */
|
||||
.detail-item {
|
||||
background: linear-gradient(135deg,
|
||||
rgba(168, 230, 207, 0.1) 0%,
|
||||
rgba(255, 255, 255, 0.1) 100%);
|
||||
backdrop-filter: blur(5px);
|
||||
border: 1px solid rgba(168, 230, 207, 0.2);
|
||||
box-shadow: 0 2px 8px rgba(39, 174, 96, 0.05);
|
||||
}
|
||||
|
||||
/* 生活指数项背景 */
|
||||
.index-item {
|
||||
background: linear-gradient(135deg,
|
||||
rgba(168, 230, 207, 0.05) 0%,
|
||||
rgba(255, 255, 255, 0.1) 100%);
|
||||
backdrop-filter: blur(5px);
|
||||
border: 1px solid rgba(168, 230, 207, 0.15);
|
||||
box-shadow: 0 2px 10px rgba(39, 174, 96, 0.05);
|
||||
}
|
||||
|
||||
.index-item:hover {
|
||||
background: linear-gradient(135deg,
|
||||
rgba(168, 230, 207, 0.1) 0%,
|
||||
rgba(255, 255, 255, 0.15) 100%);
|
||||
box-shadow: 0 5px 20px rgba(39, 174, 96, 0.1);
|
||||
}
|
||||
|
||||
/* 输入框背景 */
|
||||
#cityInput {
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
backdrop-filter: blur(10px);
|
||||
border: 2px solid rgba(168, 230, 207, 0.6);
|
||||
box-shadow: 0 2px 10px rgba(39, 174, 96, 0.1);
|
||||
}
|
||||
|
||||
#cityInput:focus {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
box-shadow:
|
||||
0 0 15px rgba(39, 174, 96, 0.2),
|
||||
0 2px 10px rgba(39, 174, 96, 0.1);
|
||||
}
|
||||
|
||||
/* 按钮背景 */
|
||||
#searchBtn {
|
||||
background: linear-gradient(135deg,
|
||||
#27ae60 0%,
|
||||
#2ecc71 50%,
|
||||
#58d68d 100%);
|
||||
box-shadow:
|
||||
0 4px 15px rgba(39, 174, 96, 0.3),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
#searchBtn:hover {
|
||||
background: linear-gradient(135deg,
|
||||
#229954 0%,
|
||||
#27ae60 50%,
|
||||
#52c370 100%);
|
||||
box-shadow:
|
||||
0 6px 20px rgba(39, 174, 96, 0.4),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
/* 错误消息背景 */
|
||||
.error-message {
|
||||
background: linear-gradient(135deg,
|
||||
rgba(231, 76, 60, 0.1) 0%,
|
||||
rgba(255, 255, 255, 0.1) 100%);
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(231, 76, 60, 0.2);
|
||||
box-shadow: 0 4px 15px rgba(231, 76, 60, 0.1);
|
||||
}
|
||||
|
||||
/* 加载状态背景 */
|
||||
.loading {
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
backdrop-filter: blur(10px);
|
||||
border-radius: 15px;
|
||||
border: 1px solid rgba(168, 230, 207, 0.3);
|
||||
box-shadow: 0 4px 15px rgba(39, 174, 96, 0.1);
|
||||
}
|
||||
|
||||
/* 移动端背景优化 */
|
||||
@media (max-width: 767px) {
|
||||
body::after {
|
||||
background-size: 100px 100px;
|
||||
animation-duration: 15s;
|
||||
}
|
||||
|
||||
.container {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
backdrop-filter: blur(3px);
|
||||
}
|
||||
|
||||
.weather-card {
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
}
|
||||
|
||||
/* 高性能设备背景增强 */
|
||||
@media (min-width: 1024px) {
|
||||
body::before {
|
||||
background-image:
|
||||
radial-gradient(circle at 20% 80%, rgba(120, 219, 226, 0.15) 0%, transparent 50%),
|
||||
radial-gradient(circle at 80% 20%, rgba(168, 230, 207, 0.15) 0%, transparent 50%),
|
||||
radial-gradient(circle at 40% 40%, rgba(220, 237, 200, 0.15) 0%, transparent 50%),
|
||||
radial-gradient(circle at 60% 70%, rgba(240, 244, 195, 0.1) 0%, transparent 50%);
|
||||
}
|
||||
|
||||
.weather-card {
|
||||
backdrop-filter: blur(20px);
|
||||
}
|
||||
|
||||
.current-weather {
|
||||
backdrop-filter: blur(15px);
|
||||
}
|
||||
}
|
||||
@@ -1,143 +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="styles.css">
|
||||
<link rel="stylesheet" href="background.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<header class="header">
|
||||
<h1>实时天气</h1>
|
||||
<div class="search-box">
|
||||
<input type="text" id="cityInput" placeholder="请输入城市名称..." value="北京">
|
||||
<button id="searchBtn">查询</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="main-content">
|
||||
<div class="loading" id="loading">正在加载天气数据...</div>
|
||||
|
||||
<div class="weather-card" id="weatherCard" style="display: none;">
|
||||
<div class="location-info">
|
||||
<h2 id="locationName">北京</h2>
|
||||
<p id="updateTime">更新时间: --</p>
|
||||
</div>
|
||||
|
||||
<div class="current-weather">
|
||||
<div class="temperature-section">
|
||||
<span class="temperature" id="temperature">--°C</span>
|
||||
<span class="weather-desc" id="weatherDesc">--</span>
|
||||
</div>
|
||||
<div class="weather-icon" id="weatherIcon">🌤️</div>
|
||||
</div>
|
||||
|
||||
<div class="weather-details">
|
||||
<div class="detail-item">
|
||||
<span class="label">体感温度</span>
|
||||
<span class="value" id="feelsLike">--°C</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span class="label">湿度</span>
|
||||
<span class="value" id="humidity">--%</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span class="label">风向</span>
|
||||
<span class="value" id="windDirection">--</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span class="label">风力</span>
|
||||
<span class="value" id="windStrength">--</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span class="label">气压</span>
|
||||
<span class="value" id="pressure">-- hPa</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span class="label">能见度</span>
|
||||
<span class="value" id="visibility">--</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span class="label">空气质量</span>
|
||||
<span class="value" id="aqi">AQI --</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span class="label">PM2.5</span>
|
||||
<span class="value" id="pm25">-- μg/m³</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="life-index">
|
||||
<h3>生活指数</h3>
|
||||
<div class="index-grid">
|
||||
<div class="index-item">
|
||||
<div class="index-icon">🌡️</div>
|
||||
<div class="index-content">
|
||||
<div class="index-title">舒适度</div>
|
||||
<div class="index-level" id="comfortLevel">--</div>
|
||||
<div class="index-desc" id="comfortDesc">--</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="index-item">
|
||||
<div class="index-icon">👕</div>
|
||||
<div class="index-content">
|
||||
<div class="index-title">穿衣指数</div>
|
||||
<div class="index-level" id="clothingLevel">--</div>
|
||||
<div class="index-desc" id="clothingDesc">--</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="index-item">
|
||||
<div class="index-icon">☂️</div>
|
||||
<div class="index-content">
|
||||
<div class="index-title">雨伞指数</div>
|
||||
<div class="index-level" id="umbrellaLevel">--</div>
|
||||
<div class="index-desc" id="umbrellaDesc">--</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="index-item">
|
||||
<div class="index-icon">☀️</div>
|
||||
<div class="index-content">
|
||||
<div class="index-title">紫外线</div>
|
||||
<div class="index-level" id="uvLevel">--</div>
|
||||
<div class="index-desc" id="uvDesc">--</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="index-item">
|
||||
<div class="index-icon">🚗</div>
|
||||
<div class="index-content">
|
||||
<div class="index-title">洗车指数</div>
|
||||
<div class="index-level" id="carWashLevel">--</div>
|
||||
<div class="index-desc" id="carWashDesc">--</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="index-item">
|
||||
<div class="index-icon">🎒</div>
|
||||
<div class="index-content">
|
||||
<div class="index-title">旅游指数</div>
|
||||
<div class="index-level" id="travelLevel">--</div>
|
||||
<div class="index-desc" id="travelDesc">--</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="index-item">
|
||||
<div class="index-icon">🏃</div>
|
||||
<div class="index-content">
|
||||
<div class="index-title">运动指数</div>
|
||||
<div class="index-level" id="sportLevel">--</div>
|
||||
<div class="index-desc" id="sportDesc">--</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="error-message" id="errorMessage" style="display: none;">
|
||||
<p>获取天气数据失败,请检查网络连接或稍后重试</p>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,354 +0,0 @@
|
||||
// 天气应用主要功能
|
||||
class WeatherApp {
|
||||
constructor() {
|
||||
this.apiUrl = 'https://60s.api.shumengya.top/v2/weather';
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
this.bindEvents();
|
||||
this.loadWeather('北京'); // 默认加载北京天气
|
||||
}
|
||||
|
||||
bindEvents() {
|
||||
const searchBtn = document.getElementById('searchBtn');
|
||||
const cityInput = document.getElementById('cityInput');
|
||||
|
||||
// 搜索按钮点击事件
|
||||
searchBtn.addEventListener('click', () => {
|
||||
const city = cityInput.value.trim();
|
||||
if (city) {
|
||||
this.loadWeather(city);
|
||||
}
|
||||
});
|
||||
|
||||
// 输入框回车事件
|
||||
cityInput.addEventListener('keypress', (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
const city = cityInput.value.trim();
|
||||
if (city) {
|
||||
this.loadWeather(city);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async loadWeather(city) {
|
||||
this.showLoading();
|
||||
|
||||
try {
|
||||
const response = await fetch(`${this.apiUrl}?query=${encodeURIComponent(city)}`);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP错误: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
console.log('完整API响应:', data); // 调试日志
|
||||
|
||||
if (data.code === 200 && data.data) {
|
||||
this.displayWeather(data.data);
|
||||
this.hideLoading();
|
||||
} else {
|
||||
throw new Error(data.message || `API返回错误: code=${data.code}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取天气数据失败:', error);
|
||||
console.error('错误详情:', {
|
||||
message: error.message,
|
||||
stack: error.stack
|
||||
});
|
||||
this.showError(error.message);
|
||||
this.hideLoading();
|
||||
}
|
||||
}
|
||||
|
||||
showLoading() {
|
||||
document.getElementById('loading').style.display = 'block';
|
||||
document.getElementById('weatherCard').style.display = 'none';
|
||||
document.getElementById('errorMessage').style.display = 'none';
|
||||
}
|
||||
|
||||
hideLoading() {
|
||||
document.getElementById('loading').style.display = 'none';
|
||||
}
|
||||
|
||||
showError(message = '获取天气数据失败,请检查网络连接或稍后重试') {
|
||||
const errorElement = document.getElementById('errorMessage');
|
||||
const errorText = errorElement.querySelector('p');
|
||||
|
||||
if (errorText) {
|
||||
errorText.textContent = message;
|
||||
}
|
||||
|
||||
errorElement.style.display = 'block';
|
||||
document.getElementById('weatherCard').style.display = 'none';
|
||||
}
|
||||
|
||||
displayWeather(data) {
|
||||
console.log('API返回数据:', data); // 调试日志
|
||||
|
||||
// 根据实际API结构解构数据
|
||||
const location = data.location || {};
|
||||
const realtime = data.realtime || {};
|
||||
const air_quality = realtime.air_quality || {};
|
||||
const life_indices = realtime.life_indices || [];
|
||||
|
||||
// 显示位置信息
|
||||
const locationName = location.formatted || location.city || location.name || '未知位置';
|
||||
document.getElementById('locationName').textContent = locationName;
|
||||
|
||||
const updateTime = realtime.updated || '未知时间';
|
||||
document.getElementById('updateTime').textContent = `更新时间: ${updateTime}`;
|
||||
|
||||
// 显示当前天气
|
||||
const temperature = realtime.temperature !== undefined ? realtime.temperature : '--';
|
||||
document.getElementById('temperature').textContent = `${temperature}°C`;
|
||||
|
||||
const condition = realtime.weather || realtime.weather_desc || '未知';
|
||||
document.getElementById('weatherDesc').textContent = condition;
|
||||
document.getElementById('weatherIcon').textContent = this.getWeatherIcon(condition);
|
||||
|
||||
// 显示天气详情
|
||||
const feelsLike = realtime.temperature_feels_like !== undefined ? realtime.temperature_feels_like : temperature;
|
||||
document.getElementById('feelsLike').textContent = `${feelsLike}°C`;
|
||||
|
||||
const humidity = realtime.humidity !== undefined ? realtime.humidity : '--';
|
||||
document.getElementById('humidity').textContent = `${humidity}%`;
|
||||
|
||||
const windDirection = realtime.wind_direction || '--';
|
||||
document.getElementById('windDirection').textContent = windDirection;
|
||||
|
||||
const windPower = realtime.wind_power || realtime.wind_strength || '--';
|
||||
document.getElementById('windStrength').textContent = windPower;
|
||||
|
||||
const pressure = realtime.pressure !== undefined ? realtime.pressure : '--';
|
||||
document.getElementById('pressure').textContent = `${pressure} hPa`;
|
||||
|
||||
document.getElementById('visibility').textContent = '--'; // API中没有能见度数据
|
||||
|
||||
const aqi = air_quality.aqi !== undefined ? air_quality.aqi : '--';
|
||||
document.getElementById('aqi').textContent = `AQI ${aqi}`;
|
||||
|
||||
const pm25 = air_quality.pm25 !== undefined ? air_quality.pm25 : '--';
|
||||
document.getElementById('pm25').textContent = `${pm25} μg/m³`;
|
||||
|
||||
// 显示生活指数
|
||||
if (life_indices && life_indices.length > 0) {
|
||||
this.displayLifeIndex(life_indices);
|
||||
} else {
|
||||
// 如果没有生活指数数据,重置显示
|
||||
this.resetLifeIndex();
|
||||
}
|
||||
|
||||
// 显示天气卡片
|
||||
document.getElementById('weatherCard').style.display = 'block';
|
||||
}
|
||||
|
||||
displayLifeIndex(lifeIndices) {
|
||||
const indexMap = {
|
||||
comfort: { level: 'comfortLevel', desc: 'comfortDesc' },
|
||||
clothes: { level: 'clothingLevel', desc: 'clothingDesc' },
|
||||
umbrella: { level: 'umbrellaLevel', desc: 'umbrellaDesc' },
|
||||
ultraviolet: { level: 'uvLevel', desc: 'uvDesc' },
|
||||
carwash: { level: 'carWashLevel', desc: 'carWashDesc' },
|
||||
tourism: { level: 'travelLevel', desc: 'travelDesc' },
|
||||
sports: { level: 'sportLevel', desc: 'sportDesc' }
|
||||
};
|
||||
|
||||
// 重置所有指数显示
|
||||
this.resetLifeIndex();
|
||||
|
||||
// 根据新的API数据结构更新生活指数
|
||||
if (Array.isArray(lifeIndices)) {
|
||||
lifeIndices.forEach(index => {
|
||||
if (index && index.key && indexMap[index.key]) {
|
||||
const { level, desc } = indexMap[index.key];
|
||||
const levelElement = document.getElementById(level);
|
||||
const descElement = document.getElementById(desc);
|
||||
|
||||
if (levelElement) levelElement.textContent = index.level || '--';
|
||||
if (descElement) descElement.textContent = index.description || '--';
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
resetLifeIndex() {
|
||||
const indexMap = {
|
||||
comfort: { level: 'comfortLevel', desc: 'comfortDesc' },
|
||||
clothes: { level: 'clothingLevel', desc: 'clothingDesc' },
|
||||
umbrella: { level: 'umbrellaLevel', desc: 'umbrellaDesc' },
|
||||
ultraviolet: { level: 'uvLevel', desc: 'uvDesc' },
|
||||
carwash: { level: 'carWashLevel', desc: 'carWashDesc' },
|
||||
tourism: { level: 'travelLevel', desc: 'travelDesc' },
|
||||
sports: { level: 'sportLevel', desc: 'sportDesc' }
|
||||
};
|
||||
|
||||
Object.values(indexMap).forEach(({ level, desc }) => {
|
||||
const levelElement = document.getElementById(level);
|
||||
const descElement = document.getElementById(desc);
|
||||
|
||||
if (levelElement) levelElement.textContent = '--';
|
||||
if (descElement) descElement.textContent = '--';
|
||||
});
|
||||
}
|
||||
|
||||
getWeatherIcon(weather) {
|
||||
const iconMap = {
|
||||
'晴': '☀️',
|
||||
'多云': '⛅',
|
||||
'阴': '☁️',
|
||||
'小雨': '🌦️',
|
||||
'中雨': '🌧️',
|
||||
'大雨': '⛈️',
|
||||
'雷阵雨': '⛈️',
|
||||
'雪': '❄️',
|
||||
'小雪': '🌨️',
|
||||
'中雪': '❄️',
|
||||
'大雪': '❄️',
|
||||
'雾': '🌫️',
|
||||
'霾': '😷',
|
||||
'沙尘暴': '🌪️'
|
||||
};
|
||||
|
||||
// 查找匹配的天气图标
|
||||
for (const [key, icon] of Object.entries(iconMap)) {
|
||||
if (weather.includes(key)) {
|
||||
return icon;
|
||||
}
|
||||
}
|
||||
|
||||
// 默认图标
|
||||
return '🌤️';
|
||||
}
|
||||
|
||||
// 获取空气质量等级颜色
|
||||
getAQIColor(aqi) {
|
||||
if (aqi <= 50) return '#00e400';
|
||||
if (aqi <= 100) return '#ffff00';
|
||||
if (aqi <= 150) return '#ff7e00';
|
||||
if (aqi <= 200) return '#ff0000';
|
||||
if (aqi <= 300) return '#8f3f97';
|
||||
return '#7e0023';
|
||||
}
|
||||
|
||||
// 格式化时间
|
||||
formatTime(timeString) {
|
||||
try {
|
||||
const date = new Date(timeString);
|
||||
return date.toLocaleString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
});
|
||||
} catch (error) {
|
||||
return timeString;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 页面加载完成后初始化应用
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
new WeatherApp();
|
||||
});
|
||||
|
||||
// 添加一些实用的工具函数
|
||||
const utils = {
|
||||
// 防抖函数
|
||||
debounce(func, wait) {
|
||||
let timeout;
|
||||
return function executedFunction(...args) {
|
||||
const later = () => {
|
||||
clearTimeout(timeout);
|
||||
func(...args);
|
||||
};
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(later, wait);
|
||||
};
|
||||
},
|
||||
|
||||
// 节流函数
|
||||
throttle(func, limit) {
|
||||
let inThrottle;
|
||||
return function() {
|
||||
const args = arguments;
|
||||
const context = this;
|
||||
if (!inThrottle) {
|
||||
func.apply(context, args);
|
||||
inThrottle = true;
|
||||
setTimeout(() => inThrottle = false, limit);
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
// 检查网络状态
|
||||
checkNetworkStatus() {
|
||||
return navigator.onLine;
|
||||
},
|
||||
|
||||
// 显示提示消息
|
||||
showToast(message, type = 'info') {
|
||||
const toast = document.createElement('div');
|
||||
toast.className = `toast toast-${type}`;
|
||||
toast.textContent = message;
|
||||
toast.style.cssText = `
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
padding: 12px 20px;
|
||||
background: ${type === 'error' ? '#e74c3c' : '#27ae60'};
|
||||
color: white;
|
||||
border-radius: 8px;
|
||||
z-index: 1000;
|
||||
animation: slideIn 0.3s ease;
|
||||
`;
|
||||
|
||||
document.body.appendChild(toast);
|
||||
|
||||
setTimeout(() => {
|
||||
toast.style.animation = 'slideOut 0.3s ease';
|
||||
setTimeout(() => {
|
||||
document.body.removeChild(toast);
|
||||
}, 300);
|
||||
}, 3000);
|
||||
}
|
||||
};
|
||||
|
||||
// 添加CSS动画
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
@keyframes slideIn {
|
||||
from {
|
||||
transform: translateX(100%);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: translateX(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideOut {
|
||||
from {
|
||||
transform: translateX(0);
|
||||
opacity: 1;
|
||||
}
|
||||
to {
|
||||
transform: translateX(100%);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
|
||||
// 网络状态监听
|
||||
window.addEventListener('online', () => {
|
||||
utils.showToast('网络连接已恢复', 'success');
|
||||
});
|
||||
|
||||
window.addEventListener('offline', () => {
|
||||
utils.showToast('网络连接已断开', 'error');
|
||||
});
|
||||
@@ -1,893 +0,0 @@
|
||||
/* 基础样式重置 */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* 隐藏滚动条但保留滚动功能 */
|
||||
html {
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
|
||||
body::-webkit-scrollbar,
|
||||
html::-webkit-scrollbar,
|
||||
*::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
|
||||
line-height: 1.6;
|
||||
color: #2c3e50;
|
||||
min-height: 100vh;
|
||||
overflow-x: hidden;
|
||||
background: linear-gradient(135deg, #f0f8e8 0%, #e8f5e8 50%, #d4f4dd 100%);
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
|
||||
/* 容器布局 */
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 头部样式 */
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-bottom: 40px;
|
||||
padding: 40px 20px;
|
||||
background: linear-gradient(135deg, #228B22 0%, #32CD32 100%);
|
||||
border-radius: 20px;
|
||||
box-shadow: 0 10px 30px rgba(34, 139, 34, 0.3);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 2.8rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 15px;
|
||||
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 1.2rem;
|
||||
opacity: 0.9;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
/* 主内容区域 */
|
||||
.main-content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 30px;
|
||||
}
|
||||
|
||||
/* 输入容器 */
|
||||
.input-container {
|
||||
background: #ffffff;
|
||||
border-radius: 20px;
|
||||
padding: 40px;
|
||||
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1);
|
||||
border: 1px solid #e8ecf4;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.input-label {
|
||||
display: block;
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.password-input-wrapper {
|
||||
position: relative;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.password-input {
|
||||
width: 100%;
|
||||
padding: 18px 60px 18px 20px;
|
||||
border: 2px solid #e8ecf4;
|
||||
border-radius: 12px;
|
||||
font-size: 1.1rem;
|
||||
font-family: 'Courier New', monospace;
|
||||
background: #f8fafc;
|
||||
transition: all 0.3s ease;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.password-input:focus {
|
||||
outline: none;
|
||||
border-color: #228B22;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 0 0 4px rgba(34, 139, 34, 0.1);
|
||||
}
|
||||
|
||||
.password-input::placeholder {
|
||||
color: #94a3b8;
|
||||
letter-spacing: normal;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
}
|
||||
|
||||
.toggle-visibility {
|
||||
position: absolute;
|
||||
right: 15px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
padding: 8px;
|
||||
border-radius: 6px;
|
||||
color: #64748b;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.toggle-visibility:hover {
|
||||
background: #f1f5f9;
|
||||
color: #475569;
|
||||
}
|
||||
|
||||
.input-hint {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
color: #64748b;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.hint-icon {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
/* 检测按钮 */
|
||||
.check-btn {
|
||||
width: 100%;
|
||||
background: linear-gradient(135deg, #228B22 0%, #32CD32 100%);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 18px 32px;
|
||||
border-radius: 12px;
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 4px 20px rgba(34, 139, 34, 0.3);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.check-btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 25px rgba(34, 139, 34, 0.4);
|
||||
}
|
||||
|
||||
.check-btn:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.check-btn:disabled {
|
||||
opacity: 0.7;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.btn-icon {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
/* 结果容器 */
|
||||
.result-container {
|
||||
background: #ffffff;
|
||||
border-radius: 20px;
|
||||
padding: 40px;
|
||||
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1);
|
||||
border: 1px solid #e8ecf4;
|
||||
animation: slideIn 0.5s ease-out;
|
||||
}
|
||||
|
||||
@keyframes slideIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* 强度概览 */
|
||||
.strength-overview {
|
||||
margin-bottom: 40px;
|
||||
padding: 30px;
|
||||
background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
|
||||
border-radius: 16px;
|
||||
border: 1px solid #e2e8f0;
|
||||
}
|
||||
|
||||
.strength-score {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 30px;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.score-circle {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
border-radius: 50%;
|
||||
background: conic-gradient(from 0deg, #e2e8f0 0deg, #e2e8f0 360deg);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
transition: all 0.5s ease;
|
||||
}
|
||||
|
||||
.score-circle::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
background: white;
|
||||
border-radius: 50%;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.score-value {
|
||||
font-size: 2.5rem;
|
||||
font-weight: 700;
|
||||
color: #2c3e50;
|
||||
z-index: 2;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.score-label {
|
||||
font-size: 0.9rem;
|
||||
color: #64748b;
|
||||
z-index: 2;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.strength-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.strength-level {
|
||||
font-size: 2rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 8px;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.strength-description {
|
||||
font-size: 1.1rem;
|
||||
color: #64748b;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.strength-bar {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.bar-background {
|
||||
width: 100%;
|
||||
height: 12px;
|
||||
background: #e2e8f0;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.bar-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #90EE90, #98FB98, #32CD32, #228B22);
|
||||
border-radius: 6px;
|
||||
width: 0%;
|
||||
transition: width 0.8s ease;
|
||||
}
|
||||
|
||||
.bar-labels {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 0.85rem;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
/* 详细信息网格 */
|
||||
.details-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 25px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.detail-card {
|
||||
background: #f8fafc;
|
||||
border-radius: 16px;
|
||||
padding: 25px;
|
||||
border: 1px solid #e2e8f0;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.detail-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.card-icon {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.card-header h3 {
|
||||
font-size: 1.3rem;
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 12px 0;
|
||||
border-bottom: 1px solid #e2e8f0;
|
||||
}
|
||||
|
||||
.info-row:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
font-weight: 500;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
/* 字符类型分析 */
|
||||
.character-types {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 12px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.char-type {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 10px 12px;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #e2e8f0;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.char-type.has-type {
|
||||
background: #f0f8e8;
|
||||
border-color: #d4f4dd;
|
||||
color: #1e7e1e;
|
||||
}
|
||||
|
||||
.char-type.has-type .type-icon {
|
||||
color: #228B22;
|
||||
}
|
||||
|
||||
.type-icon {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.character-issues {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.issue-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px 12px;
|
||||
background: #fef2f2;
|
||||
border: 1px solid #fecaca;
|
||||
border-radius: 8px;
|
||||
color: #dc2626;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.issue-item.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.issue-icon {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
/* 建议和提示区域 */
|
||||
.recommendations-section {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
|
||||
gap: 25px;
|
||||
}
|
||||
|
||||
.recommendations-card,
|
||||
.security-tips-card {
|
||||
background: #f8fafc;
|
||||
border-radius: 16px;
|
||||
padding: 25px;
|
||||
border: 1px solid #e2e8f0;
|
||||
}
|
||||
|
||||
.recommendations-list {
|
||||
list-style: none;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.recommendations-list li {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
padding: 12px 16px;
|
||||
background: white;
|
||||
border-radius: 10px;
|
||||
border: 1px solid #e2e8f0;
|
||||
color: #2c3e50;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.recommendations-list li::before {
|
||||
content: '💡';
|
||||
font-size: 1rem;
|
||||
margin-top: 2px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.tips-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.tip-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
padding: 12px 16px;
|
||||
background: white;
|
||||
border-radius: 10px;
|
||||
border: 1px solid #e2e8f0;
|
||||
color: #2c3e50;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.tip-icon {
|
||||
font-size: 1rem;
|
||||
margin-top: 2px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* 错误容器 */
|
||||
.error-container {
|
||||
background: #ffffff;
|
||||
border-radius: 20px;
|
||||
padding: 50px 40px;
|
||||
text-align: center;
|
||||
box-shadow: 0 10px 40px rgba(239, 68, 68, 0.1);
|
||||
border: 1px solid #fecaca;
|
||||
}
|
||||
|
||||
.error-icon {
|
||||
font-size: 4rem;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.error-container h3 {
|
||||
color: #dc2626;
|
||||
margin-bottom: 15px;
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.error-container p {
|
||||
color: #64748b;
|
||||
margin-bottom: 25px;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.retry-btn {
|
||||
background: #dc2626;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 14px 28px;
|
||||
border-radius: 10px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.retry-btn:hover {
|
||||
background: #b91c1c;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
/* 页脚 */
|
||||
.footer {
|
||||
text-align: center;
|
||||
padding: 40px 20px;
|
||||
color: #64748b;
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.footer p {
|
||||
margin-bottom: 8px;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.footer-note {
|
||||
font-size: 0.9rem;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
/* 提示框 */
|
||||
.toast {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
background: #228B22;
|
||||
color: white;
|
||||
padding: 16px 24px;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 4px 20px rgba(34, 139, 34, 0.3);
|
||||
z-index: 1000;
|
||||
animation: toastSlide 0.3s ease-out;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
@keyframes toastSlide {
|
||||
from {
|
||||
transform: translateX(100%);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: translateX(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* 强度等级颜色 */
|
||||
.strength-weak {
|
||||
color: #dc2626 !important;
|
||||
}
|
||||
|
||||
.strength-medium {
|
||||
color: #f59e0b !important;
|
||||
}
|
||||
|
||||
.strength-strong {
|
||||
color: #228B22 !important;
|
||||
}
|
||||
|
||||
.strength-very-strong {
|
||||
color: #1e7e1e !important;
|
||||
}
|
||||
|
||||
/* 分数圆圈颜色 */
|
||||
.score-weak {
|
||||
background: conic-gradient(from 0deg, #dc2626 0deg, #dc2626 var(--score-deg), #e2e8f0 var(--score-deg), #e2e8f0 360deg) !important;
|
||||
}
|
||||
|
||||
.score-medium {
|
||||
background: conic-gradient(from 0deg, #f59e0b 0deg, #f59e0b var(--score-deg), #e2e8f0 var(--score-deg), #e2e8f0 360deg) !important;
|
||||
}
|
||||
|
||||
.score-strong {
|
||||
background: conic-gradient(from 0deg, #228B22 0deg, #228B22 var(--score-deg), #e2e8f0 var(--score-deg), #e2e8f0 360deg) !important;
|
||||
}
|
||||
|
||||
.score-very-strong {
|
||||
background: conic-gradient(from 0deg, #1e7e1e 0deg, #1e7e1e var(--score-deg), #e2e8f0 var(--score-deg), #e2e8f0 360deg) !important;
|
||||
}
|
||||
|
||||
/* 平板端适配 (768px - 1024px) */
|
||||
@media (min-width: 768px) and (max-width: 1024px) {
|
||||
.container {
|
||||
max-width: 900px;
|
||||
padding: 25px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 2.4rem;
|
||||
}
|
||||
|
||||
.input-container,
|
||||
.result-container {
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
.details-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.recommendations-section {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.strength-score {
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
gap: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 手机端适配 (最大767px) */
|
||||
@media (max-width: 767px) {
|
||||
.container {
|
||||
padding: 15px;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: 25px 15px;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.input-container,
|
||||
.result-container {
|
||||
padding: 25px;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.password-input {
|
||||
padding: 16px 50px 16px 16px;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.check-btn {
|
||||
padding: 16px 28px;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.strength-overview {
|
||||
padding: 20px;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.strength-score {
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.score-circle {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.score-circle::before {
|
||||
width: 75px;
|
||||
height: 75px;
|
||||
}
|
||||
|
||||
.score-value {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.strength-level {
|
||||
font-size: 1.6rem;
|
||||
}
|
||||
|
||||
.details-grid {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.detail-card {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.character-types {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.recommendations-section {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.recommendations-card,
|
||||
.security-tips-card {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.toast {
|
||||
right: 15px;
|
||||
left: 15px;
|
||||
top: 15px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
/* 小屏手机适配 (最大480px) */
|
||||
@media (max-width: 480px) {
|
||||
.container {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: 20px 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
.input-container,
|
||||
.result-container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.password-input {
|
||||
padding: 14px 45px 14px 14px;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.check-btn {
|
||||
padding: 14px 24px;
|
||||
}
|
||||
|
||||
.detail-card {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.card-header h3 {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* 触摸设备优化 */
|
||||
@media (hover: none) and (pointer: coarse) {
|
||||
.check-btn,
|
||||
.retry-btn,
|
||||
.toggle-visibility {
|
||||
min-height: 44px;
|
||||
}
|
||||
|
||||
.toggle-visibility {
|
||||
padding: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 高对比度模式支持 */
|
||||
@media (prefers-contrast: high) {
|
||||
.input-container,
|
||||
.result-container,
|
||||
.detail-card {
|
||||
border: 2px solid #2c3e50;
|
||||
}
|
||||
|
||||
.password-input {
|
||||
border: 2px solid #2c3e50;
|
||||
}
|
||||
}
|
||||
|
||||
/* 减少动画模式支持 */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
* {
|
||||
animation-duration: 0.01ms !important;
|
||||
animation-iteration-count: 1 !important;
|
||||
transition-duration: 0.01ms !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* 深色模式支持 */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
background: #0f172a;
|
||||
color: #e2e8f0;
|
||||
}
|
||||
|
||||
.input-container,
|
||||
.result-container,
|
||||
.detail-card,
|
||||
.recommendations-card,
|
||||
.security-tips-card {
|
||||
background: #1e293b;
|
||||
border-color: #334155;
|
||||
}
|
||||
|
||||
.password-input {
|
||||
background: #334155;
|
||||
border-color: #475569;
|
||||
color: #e2e8f0;
|
||||
}
|
||||
|
||||
.password-input:focus {
|
||||
background: #1e293b;
|
||||
border-color: #667eea;
|
||||
}
|
||||
|
||||
.strength-overview {
|
||||
background: #1e293b;
|
||||
border-color: #334155;
|
||||
}
|
||||
|
||||
.char-type,
|
||||
.recommendations-list li,
|
||||
.tip-item {
|
||||
background: #334155;
|
||||
border-color: #475569;
|
||||
color: #e2e8f0;
|
||||
}
|
||||
}
|
||||
|
||||
/* 打印样式 */
|
||||
@media print {
|
||||
.header {
|
||||
background: none !important;
|
||||
color: black !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.check-btn,
|
||||
.retry-btn,
|
||||
.toggle-visibility,
|
||||
.toast {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.input-container,
|
||||
.result-container {
|
||||
box-shadow: none !important;
|
||||
border: 1px solid #ccc !important;
|
||||
}
|
||||
}
|
||||
@@ -1,218 +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 name="description" content="在线密码强度检测工具,实时分析密码安全性,提供专业的密码安全建议">
|
||||
<meta name="keywords" content="密码强度检测,密码安全,密码分析,在线工具">
|
||||
<title>🔒 密码强度检测器</title>
|
||||
<link rel="preconnect" href="https://60s.api.shumengya.top">
|
||||
<link rel="dns-prefetch" href="https://60s.api.shumengya.top">
|
||||
<link rel="stylesheet" href="css/style.css">
|
||||
<link rel="stylesheet" href="css/background.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<header class="header">
|
||||
<h1>🔒 密码强度检测器</h1>
|
||||
<p class="subtitle">实时分析密码安全性,保护您的数字生活</p>
|
||||
</header>
|
||||
|
||||
<main class="main-content">
|
||||
<!-- 密码输入区域 -->
|
||||
<div class="input-container">
|
||||
<div class="input-group">
|
||||
<label for="passwordInput" class="input-label">请输入要检测的密码</label>
|
||||
<div class="password-input-wrapper">
|
||||
<input
|
||||
type="password"
|
||||
id="passwordInput"
|
||||
class="password-input"
|
||||
placeholder="输入您的密码进行安全性检测..."
|
||||
autocomplete="new-password"
|
||||
spellcheck="false"
|
||||
>
|
||||
<button type="button" class="toggle-visibility" id="toggleVisibility" title="显示/隐藏密码">
|
||||
<svg class="eye-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path>
|
||||
<circle cx="12" cy="12" r="3"></circle>
|
||||
</svg>
|
||||
<svg class="eye-off-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="display: none;">
|
||||
<path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"></path>
|
||||
<line x1="1" y1="1" x2="23" y2="23"></line>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="input-hint">
|
||||
<span class="hint-icon">💡</span>
|
||||
<span class="hint-text">输入密码后将实时显示安全性分析结果</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="button" class="check-btn" id="checkBtn">
|
||||
<span class="btn-icon">🔍</span>
|
||||
<span class="btn-text">检测密码强度</span>
|
||||
<span class="btn-loading" style="display: none;">检测中...</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 结果显示区域 -->
|
||||
<div class="result-container" id="resultContainer" style="display: none;">
|
||||
<!-- 密码强度概览 -->
|
||||
<div class="strength-overview">
|
||||
<div class="strength-score">
|
||||
<div class="score-circle" id="scoreCircle">
|
||||
<div class="score-value" id="scoreValue">0</div>
|
||||
<div class="score-label">分</div>
|
||||
</div>
|
||||
<div class="strength-info">
|
||||
<div class="strength-level" id="strengthLevel">未知</div>
|
||||
<div class="strength-description" id="strengthDescription">请输入密码进行检测</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="strength-bar">
|
||||
<div class="bar-background">
|
||||
<div class="bar-fill" id="strengthBar"></div>
|
||||
</div>
|
||||
<div class="bar-labels">
|
||||
<span>弱</span>
|
||||
<span>中等</span>
|
||||
<span>强</span>
|
||||
<span>非常强</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 详细信息 -->
|
||||
<div class="details-grid">
|
||||
<div class="detail-card">
|
||||
<div class="card-header">
|
||||
<span class="card-icon">📏</span>
|
||||
<h3>基本信息</h3>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<div class="info-row">
|
||||
<span class="info-label">密码长度:</span>
|
||||
<span class="info-value" id="passwordLength">-</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">熵值:</span>
|
||||
<span class="info-value" id="entropyValue">-</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">破解时间:</span>
|
||||
<span class="info-value" id="crackTime">-</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">字符种类:</span>
|
||||
<span class="info-value" id="characterVariety">-</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-card">
|
||||
<div class="card-header">
|
||||
<span class="card-icon">🔤</span>
|
||||
<h3>字符分析</h3>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<div class="character-types" id="characterTypes">
|
||||
<div class="char-type" id="hasLowercase">
|
||||
<span class="type-icon">❌</span>
|
||||
<span class="type-text">小写字母</span>
|
||||
</div>
|
||||
<div class="char-type" id="hasUppercase">
|
||||
<span class="type-icon">❌</span>
|
||||
<span class="type-text">大写字母</span>
|
||||
</div>
|
||||
<div class="char-type" id="hasNumbers">
|
||||
<span class="type-icon">❌</span>
|
||||
<span class="type-text">数字</span>
|
||||
</div>
|
||||
<div class="char-type" id="hasSymbols">
|
||||
<span class="type-icon">❌</span>
|
||||
<span class="type-text">特殊符号</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="character-issues" id="characterIssues">
|
||||
<div class="issue-item" id="hasRepeated">
|
||||
<span class="issue-icon">⚠️</span>
|
||||
<span class="issue-text">包含重复字符</span>
|
||||
</div>
|
||||
<div class="issue-item" id="hasSequential">
|
||||
<span class="issue-icon">⚠️</span>
|
||||
<span class="issue-text">包含连续字符</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 建议和提示 -->
|
||||
<div class="recommendations-section">
|
||||
<div class="recommendations-card">
|
||||
<div class="card-header">
|
||||
<span class="card-icon">💡</span>
|
||||
<h3>改进建议</h3>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<ul class="recommendations-list" id="recommendationsList">
|
||||
<li>请输入密码进行分析</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="security-tips-card">
|
||||
<div class="card-header">
|
||||
<span class="card-icon">🛡️</span>
|
||||
<h3>安全提示</h3>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<div class="tips-container" id="securityTips">
|
||||
<div class="tip-item">
|
||||
<span class="tip-icon">🔐</span>
|
||||
<span class="tip-text">使用密码管理器生成和存储复杂密码</span>
|
||||
</div>
|
||||
<div class="tip-item">
|
||||
<span class="tip-icon">🔄</span>
|
||||
<span class="tip-text">为不同账户使用不同的密码</span>
|
||||
</div>
|
||||
<div class="tip-item">
|
||||
<span class="tip-icon">⏰</span>
|
||||
<span class="tip-text">定期更换重要账户的密码</span>
|
||||
</div>
|
||||
<div class="tip-item">
|
||||
<span class="tip-icon">🔒</span>
|
||||
<span class="tip-text">启用双因素认证(2FA)增强安全性</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 错误显示区域 -->
|
||||
<div class="error-container" id="errorContainer" style="display: none;">
|
||||
<div class="error-icon">⚠️</div>
|
||||
<h3>检测失败</h3>
|
||||
<p id="errorMessage">请检查网络连接后重试</p>
|
||||
<button class="retry-btn" id="retryBtn">重新检测</button>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="footer">
|
||||
<p>🔒 保护您的数字安全,从强密码开始</p>
|
||||
<p class="footer-note">本工具不会存储您的密码信息</p>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<!-- 提示框 -->
|
||||
<div class="toast" id="toast" style="display: none;">
|
||||
<span id="toastMessage">操作成功</span>
|
||||
</div>
|
||||
|
||||
<script src="js/script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,516 +0,0 @@
|
||||
/**
|
||||
* 密码强度检测器
|
||||
* 提供密码强度分析和安全建议
|
||||
*/
|
||||
class PasswordStrengthChecker {
|
||||
constructor() {
|
||||
this.apiUrl = 'https://60s.api.shumengya.top/v2/password/check';
|
||||
this.isChecking = false;
|
||||
this.currentPassword = '';
|
||||
this.init();
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化应用
|
||||
*/
|
||||
init() {
|
||||
this.bindEvents();
|
||||
this.setupFormValidation();
|
||||
this.hideResultContainer();
|
||||
this.hideErrorContainer();
|
||||
console.log('密码强度检测器初始化完成');
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定事件监听器
|
||||
*/
|
||||
bindEvents() {
|
||||
// 密码输入框事件
|
||||
const passwordInput = document.getElementById('passwordInput');
|
||||
if (passwordInput) {
|
||||
passwordInput.addEventListener('input', this.handlePasswordInput.bind(this));
|
||||
passwordInput.addEventListener('keypress', this.handleKeyPress.bind(this));
|
||||
}
|
||||
|
||||
// 显示/隐藏密码按钮
|
||||
const toggleBtn = document.getElementById('toggleVisibility');
|
||||
if (toggleBtn) {
|
||||
toggleBtn.addEventListener('click', this.togglePasswordVisibility.bind(this));
|
||||
}
|
||||
|
||||
// 检测按钮
|
||||
const checkBtn = document.getElementById('checkBtn');
|
||||
if (checkBtn) {
|
||||
checkBtn.addEventListener('click', this.handleCheckPassword.bind(this));
|
||||
}
|
||||
|
||||
// 重试按钮
|
||||
const retryBtn = document.getElementById('retryBtn');
|
||||
if (retryBtn) {
|
||||
retryBtn.addEventListener('click', this.handleRetry.bind(this));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置表单验证
|
||||
*/
|
||||
setupFormValidation() {
|
||||
const form = document.querySelector('.input-container');
|
||||
if (form) {
|
||||
form.addEventListener('submit', (e) => {
|
||||
e.preventDefault();
|
||||
this.handleCheckPassword();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理密码输入
|
||||
*/
|
||||
handlePasswordInput(event) {
|
||||
const password = event.target.value;
|
||||
this.currentPassword = password;
|
||||
|
||||
// 更新按钮状态
|
||||
this.updateCheckButtonState();
|
||||
|
||||
// 如果密码为空,隐藏结果
|
||||
if (!password.trim()) {
|
||||
this.hideResultContainer();
|
||||
this.hideErrorContainer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理键盘事件
|
||||
*/
|
||||
handleKeyPress(event) {
|
||||
if (event.key === 'Enter' && !this.isChecking) {
|
||||
event.preventDefault();
|
||||
this.handleCheckPassword();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换密码可见性
|
||||
*/
|
||||
togglePasswordVisibility() {
|
||||
const passwordInput = document.getElementById('passwordInput');
|
||||
const toggleBtn = document.getElementById('toggleVisibility');
|
||||
|
||||
if (passwordInput && toggleBtn) {
|
||||
const isPassword = passwordInput.type === 'password';
|
||||
passwordInput.type = isPassword ? 'text' : 'password';
|
||||
toggleBtn.innerHTML = isPassword ? '🙈' : '👁️';
|
||||
toggleBtn.title = isPassword ? '隐藏密码' : '显示密码';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新检测按钮状态
|
||||
*/
|
||||
updateCheckButtonState() {
|
||||
const checkBtn = document.getElementById('checkBtn');
|
||||
const hasPassword = this.currentPassword.trim().length > 0;
|
||||
|
||||
if (checkBtn) {
|
||||
checkBtn.disabled = !hasPassword || this.isChecking;
|
||||
|
||||
if (this.isChecking) {
|
||||
checkBtn.innerHTML = '<span class="btn-icon">⏳</span>检测中...';
|
||||
} else if (hasPassword) {
|
||||
checkBtn.innerHTML = '<span class="btn-icon">🔍</span>检测密码强度';
|
||||
} else {
|
||||
checkBtn.innerHTML = '<span class="btn-icon">🔍</span>请输入密码';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理密码检测
|
||||
*/
|
||||
async handleCheckPassword() {
|
||||
const password = this.currentPassword.trim();
|
||||
|
||||
if (!password) {
|
||||
this.showToast('请输入要检测的密码', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.isChecking) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.setLoadingState(true);
|
||||
this.hideErrorContainer();
|
||||
|
||||
const result = await this.checkPasswordStrength(password);
|
||||
|
||||
if (result.code === 200 && result.data) {
|
||||
this.displayResults(result.data);
|
||||
this.showResultContainer();
|
||||
this.showToast('密码强度检测完成', 'success');
|
||||
} else {
|
||||
throw new Error(result.message || '检测失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('密码检测错误:', error);
|
||||
this.showError(error.message || '检测服务暂时不可用,请稍后重试');
|
||||
} finally {
|
||||
this.setLoadingState(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用API检测密码强度
|
||||
*/
|
||||
async checkPasswordStrength(password) {
|
||||
const url = new URL(this.apiUrl);
|
||||
url.searchParams.append('password', password);
|
||||
url.searchParams.append('encoding', 'utf-8');
|
||||
|
||||
const response = await fetch(url.toString(), {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示检测结果
|
||||
*/
|
||||
displayResults(data) {
|
||||
this.updateStrengthOverview(data);
|
||||
this.updateDetailedInfo(data);
|
||||
this.updateRecommendations(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新强度概览
|
||||
*/
|
||||
updateStrengthOverview(data) {
|
||||
// 更新分数圆圈
|
||||
const scoreCircle = document.getElementById('scoreCircle');
|
||||
const scoreValue = document.getElementById('scoreValue');
|
||||
const strengthLevel = document.getElementById('strengthLevel');
|
||||
const strengthDescription = document.getElementById('strengthDescription');
|
||||
const barFill = document.getElementById('strengthBar');
|
||||
|
||||
if (scoreValue) {
|
||||
scoreValue.textContent = data.score || 0;
|
||||
}
|
||||
|
||||
if (strengthLevel) {
|
||||
strengthLevel.textContent = this.getStrengthText(data.strength);
|
||||
const strengthClass = this.getStrengthClass(data.strength);
|
||||
strengthLevel.className = `strength-level strength-${strengthClass}`;
|
||||
}
|
||||
|
||||
if (strengthDescription) {
|
||||
strengthDescription.textContent = this.getStrengthDescription(data.strength);
|
||||
}
|
||||
|
||||
// 更新分数圆圈
|
||||
if (scoreCircle) {
|
||||
const percentage = (data.score / 100) * 360;
|
||||
scoreCircle.style.setProperty('--score-deg', `${percentage}deg`);
|
||||
// 将中文强度转换为CSS类名
|
||||
const strengthClass = this.getStrengthClass(data.strength);
|
||||
scoreCircle.className = `score-circle score-${strengthClass}`;
|
||||
}
|
||||
|
||||
// 更新强度条
|
||||
if (barFill) {
|
||||
setTimeout(() => {
|
||||
barFill.style.width = `${data.score}%`;
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新详细信息
|
||||
*/
|
||||
updateDetailedInfo(data) {
|
||||
// 基本信息
|
||||
this.updateElement('passwordLength', data.length || 0);
|
||||
this.updateElement('entropyValue', data.entropy ? data.entropy.toFixed(2) : '0.00');
|
||||
this.updateElement('crackTime', data.time_to_crack || '未知');
|
||||
|
||||
// 字符类型分析
|
||||
this.updateCharacterAnalysis(data.character_analysis || {});
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新字符类型分析
|
||||
*/
|
||||
updateCharacterAnalysis(analysis) {
|
||||
const types = {
|
||||
'has_lowercase': { element: 'hasLowercase', label: '小写字母', icon: '🔤' },
|
||||
'has_uppercase': { element: 'hasUppercase', label: '大写字母', icon: '🔠' },
|
||||
'has_numbers': { element: 'hasNumbers', label: '数字', icon: '🔢' },
|
||||
'has_symbols': { element: 'hasSymbols', label: '特殊符号', icon: '🔣' }
|
||||
};
|
||||
|
||||
Object.keys(types).forEach(key => {
|
||||
const element = document.getElementById(types[key].element);
|
||||
if (element) {
|
||||
const hasType = analysis[key] || false;
|
||||
element.className = `char-type ${hasType ? 'has-type' : ''}`;
|
||||
element.innerHTML = `
|
||||
<span class="type-icon">${hasType ? '✅' : '❌'}</span>
|
||||
<span>${types[key].label}</span>
|
||||
`;
|
||||
}
|
||||
});
|
||||
|
||||
// 更新字符种类数量
|
||||
this.updateElement('characterVariety', analysis.character_variety || 0);
|
||||
|
||||
// 更新问题提示
|
||||
this.updateCharacterIssues(analysis);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新字符问题提示
|
||||
*/
|
||||
updateCharacterIssues(analysis) {
|
||||
const issues = [
|
||||
{ id: 'hasRepeated', condition: analysis.has_repeated, text: '包含重复字符' },
|
||||
{ id: 'hasSequential', condition: analysis.has_sequential, text: '包含连续字符' }
|
||||
];
|
||||
|
||||
issues.forEach(issue => {
|
||||
const element = document.getElementById(issue.id);
|
||||
if (element) {
|
||||
if (issue.condition) {
|
||||
element.style.display = 'flex';
|
||||
element.innerHTML = `
|
||||
<span class="issue-icon">⚠️</span>
|
||||
<span class="issue-text">${issue.text}</span>
|
||||
`;
|
||||
} else {
|
||||
element.style.display = 'none';
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新建议和提示
|
||||
*/
|
||||
updateRecommendations(data) {
|
||||
// 更新建议列表
|
||||
const recommendationsList = document.getElementById('recommendationsList');
|
||||
if (recommendationsList && data.recommendations) {
|
||||
recommendationsList.innerHTML = '';
|
||||
data.recommendations.forEach(recommendation => {
|
||||
const li = document.createElement('li');
|
||||
li.textContent = recommendation;
|
||||
recommendationsList.appendChild(li);
|
||||
});
|
||||
}
|
||||
|
||||
// 更新安全提示
|
||||
const tipsContainer = document.getElementById('securityTips');
|
||||
if (tipsContainer && data.security_tips) {
|
||||
tipsContainer.innerHTML = '';
|
||||
data.security_tips.forEach((tip, index) => {
|
||||
const tipElement = document.createElement('div');
|
||||
tipElement.className = 'tip-item';
|
||||
tipElement.innerHTML = `
|
||||
<span class="tip-icon">${this.getTipIcon(index)}</span>
|
||||
<span class="tip-text">${tip}</span>
|
||||
`;
|
||||
tipsContainer.appendChild(tipElement);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取提示图标
|
||||
*/
|
||||
getTipIcon(index) {
|
||||
const icons = ['🛡️', '🔐', '⚡', '🎯', '💡', '🔄'];
|
||||
return icons[index % icons.length];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取强度文本
|
||||
*/
|
||||
getStrengthText(strength) {
|
||||
// API直接返回中文强度,无需映射
|
||||
return strength || '未知';
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取强度CSS类名
|
||||
*/
|
||||
getStrengthClass(strength) {
|
||||
const classMap = {
|
||||
'弱': 'weak',
|
||||
'中等': 'medium',
|
||||
'强': 'strong',
|
||||
'非常强': 'very-strong'
|
||||
};
|
||||
return classMap[strength] || 'unknown';
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取强度描述
|
||||
*/
|
||||
getStrengthDescription(strength) {
|
||||
const descriptions = {
|
||||
'弱': '密码强度较弱,建议增加复杂度',
|
||||
'中等': '密码强度中等,可以进一步优化',
|
||||
'强': '密码强度良好,安全性较高',
|
||||
'非常强': '密码强度非常好,安全性很高'
|
||||
};
|
||||
return descriptions[strength] || '无法评估密码强度';
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置加载状态
|
||||
*/
|
||||
setLoadingState(loading) {
|
||||
this.isChecking = loading;
|
||||
this.updateCheckButtonState();
|
||||
|
||||
const passwordInput = document.getElementById('passwordInput');
|
||||
if (passwordInput) {
|
||||
passwordInput.disabled = loading;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示结果容器
|
||||
*/
|
||||
showResultContainer() {
|
||||
const container = document.getElementById('resultContainer');
|
||||
if (container) {
|
||||
container.style.display = 'block';
|
||||
container.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 隐藏结果容器
|
||||
*/
|
||||
hideResultContainer() {
|
||||
const container = document.getElementById('resultContainer');
|
||||
if (container) {
|
||||
container.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示错误
|
||||
*/
|
||||
showError(message) {
|
||||
const errorContainer = document.getElementById('errorContainer');
|
||||
const errorMessage = document.getElementById('errorMessage');
|
||||
|
||||
if (errorContainer && errorMessage) {
|
||||
errorMessage.textContent = message;
|
||||
errorContainer.style.display = 'block';
|
||||
this.hideResultContainer();
|
||||
errorContainer.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 隐藏错误容器
|
||||
*/
|
||||
hideErrorContainer() {
|
||||
const container = document.getElementById('errorContainer');
|
||||
if (container) {
|
||||
container.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理重试
|
||||
*/
|
||||
handleRetry() {
|
||||
this.hideErrorContainer();
|
||||
const passwordInput = document.getElementById('passwordInput');
|
||||
if (passwordInput) {
|
||||
passwordInput.focus();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新元素内容
|
||||
*/
|
||||
updateElement(id, content) {
|
||||
const element = document.getElementById(id);
|
||||
if (element) {
|
||||
element.textContent = content;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示提示消息
|
||||
*/
|
||||
showToast(message, type = 'success') {
|
||||
const toast = document.getElementById('toast');
|
||||
const toastMessage = document.getElementById('toastMessage');
|
||||
|
||||
if (toast && toastMessage) {
|
||||
toastMessage.textContent = message;
|
||||
toast.className = `toast toast-${type}`;
|
||||
toast.style.display = 'block';
|
||||
|
||||
// 3秒后自动隐藏
|
||||
setTimeout(() => {
|
||||
toast.style.display = 'none';
|
||||
}, 3000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 页面加载完成后初始化
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
try {
|
||||
window.passwordChecker = new PasswordStrengthChecker();
|
||||
console.log('密码强度检测器已启动');
|
||||
} catch (error) {
|
||||
console.error('初始化失败:', error);
|
||||
}
|
||||
});
|
||||
|
||||
// 页面可见性变化处理
|
||||
document.addEventListener('visibilitychange', () => {
|
||||
if (document.visibilityState === 'visible' && window.passwordChecker) {
|
||||
console.log('页面重新激活');
|
||||
}
|
||||
});
|
||||
|
||||
// 全局错误处理
|
||||
window.addEventListener('error', (event) => {
|
||||
console.error('全局错误:', event.error);
|
||||
if (window.passwordChecker) {
|
||||
window.passwordChecker.showToast('发生了意外错误,请刷新页面重试', 'error');
|
||||
}
|
||||
});
|
||||
|
||||
// 网络状态监听
|
||||
window.addEventListener('online', () => {
|
||||
if (window.passwordChecker) {
|
||||
window.passwordChecker.showToast('网络连接已恢复', 'success');
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener('offline', () => {
|
||||
if (window.passwordChecker) {
|
||||
window.passwordChecker.showToast('网络连接已断开', 'error');
|
||||
}
|
||||
});
|
||||
@@ -1,37 +0,0 @@
|
||||
{
|
||||
"code": 200,
|
||||
"message": "获取成功。数据来自官方/权威源头,以确保稳定与实时。开源地址 https://github.com/vikiboss/60s,反馈群 595941841",
|
||||
"data": {
|
||||
"password": "adasdasdasdadasd",
|
||||
"length": 16,
|
||||
"score": 68,
|
||||
"strength": "中等",
|
||||
"entropy": 75.21,
|
||||
"time_to_crack": "数百万年",
|
||||
"character_analysis": {
|
||||
"has_lowercase": true,
|
||||
"has_uppercase": false,
|
||||
"has_numbers": false,
|
||||
"has_symbols": false,
|
||||
"has_repeated": false,
|
||||
"has_sequential": true,
|
||||
"character_variety": 26
|
||||
},
|
||||
"recommendations": [
|
||||
"建议包含大写字母",
|
||||
"建议包含数字",
|
||||
"建议包含特殊符号",
|
||||
"避免使用连续序列字符"
|
||||
],
|
||||
"security_tips": [
|
||||
"使用密码管理器生成和存储复杂密码",
|
||||
"为不同账户使用不同的密码",
|
||||
"定期更换重要账户的密码",
|
||||
"启用双因素认证(2FA)增强安全性",
|
||||
"避免在公共场合输入密码",
|
||||
"不要将密码保存在浏览器中(除非使用可信的密码管理器)",
|
||||
"避免使用个人信息作为密码",
|
||||
"长密码比复杂密码更安全"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,243 +0,0 @@
|
||||
/* 背景样式文件 - 独立管理背景相关样式 */
|
||||
|
||||
/* 主体背景 */
|
||||
body {
|
||||
background: linear-gradient(135deg, #e8f5e8 0%, #f0f8f0 25%, #e1f5e1 50%, #f5f9f5 75%, #e8f5e8 100%);
|
||||
background-size: 400% 400%;
|
||||
animation: gradientShift 15s ease infinite;
|
||||
position: relative;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
/* 背景动画 */
|
||||
@keyframes gradientShift {
|
||||
0% {
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
50% {
|
||||
background-position: 100% 50%;
|
||||
}
|
||||
100% {
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
}
|
||||
|
||||
/* 装饰性背景元素 */
|
||||
body::before {
|
||||
content: '';
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image:
|
||||
radial-gradient(circle at 20% 80%, rgba(144, 238, 144, 0.1) 0%, transparent 50%),
|
||||
radial-gradient(circle at 80% 20%, rgba(152, 251, 152, 0.1) 0%, transparent 50%),
|
||||
radial-gradient(circle at 40% 40%, rgba(173, 255, 173, 0.08) 0%, transparent 50%);
|
||||
pointer-events: none;
|
||||
z-index: -2;
|
||||
}
|
||||
|
||||
/* 浮动装饰圆点 */
|
||||
body::after {
|
||||
content: '';
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image:
|
||||
radial-gradient(2px 2px at 20px 30px, rgba(76, 175, 80, 0.3), transparent),
|
||||
radial-gradient(2px 2px at 40px 70px, rgba(129, 199, 132, 0.3), transparent),
|
||||
radial-gradient(1px 1px at 90px 40px, rgba(165, 214, 167, 0.3), transparent),
|
||||
radial-gradient(1px 1px at 130px 80px, rgba(200, 230, 201, 0.3), transparent),
|
||||
radial-gradient(2px 2px at 160px 30px, rgba(76, 175, 80, 0.2), transparent);
|
||||
background-repeat: repeat;
|
||||
background-size: 200px 100px;
|
||||
animation: floatDots 20s linear infinite;
|
||||
pointer-events: none;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
@keyframes floatDots {
|
||||
0% {
|
||||
transform: translateY(0px);
|
||||
}
|
||||
100% {
|
||||
transform: translateY(-100px);
|
||||
}
|
||||
}
|
||||
|
||||
/* 容器背景增强 */
|
||||
.container {
|
||||
background: rgba(255, 255, 255, 0.02);
|
||||
backdrop-filter: blur(10px);
|
||||
border-radius: 20px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* 表单区域背景 */
|
||||
.form-section {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
backdrop-filter: blur(20px);
|
||||
border: 1px solid rgba(144, 238, 144, 0.3);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.form-section::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -50%;
|
||||
left: -50%;
|
||||
width: 200%;
|
||||
height: 200%;
|
||||
background: linear-gradient(45deg, transparent, rgba(144, 238, 144, 0.05), transparent);
|
||||
animation: shimmer 3s ease-in-out infinite;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@keyframes shimmer {
|
||||
0% {
|
||||
transform: translateX(-100%) translateY(-100%) rotate(45deg);
|
||||
}
|
||||
50% {
|
||||
transform: translateX(100%) translateY(100%) rotate(45deg);
|
||||
}
|
||||
100% {
|
||||
transform: translateX(-100%) translateY(-100%) rotate(45deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* 结果卡片背景 */
|
||||
.basic-info-card,
|
||||
.bmi-card,
|
||||
.weight-card,
|
||||
.metabolism-card,
|
||||
.body-fat-card,
|
||||
.measurements-card,
|
||||
.advice-card {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
backdrop-filter: blur(15px);
|
||||
border: 1px solid rgba(144, 238, 144, 0.2);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 卡片悬停背景效果 */
|
||||
.basic-info-card:hover,
|
||||
.bmi-card:hover,
|
||||
.weight-card:hover,
|
||||
.metabolism-card:hover,
|
||||
.body-fat-card:hover,
|
||||
.measurements-card:hover,
|
||||
.advice-card:hover {
|
||||
background: rgba(255, 255, 255, 0.98);
|
||||
border-color: rgba(76, 175, 80, 0.4);
|
||||
}
|
||||
|
||||
/* 免责声明卡片背景 */
|
||||
.disclaimer-card {
|
||||
background: rgba(255, 243, 205, 0.95);
|
||||
backdrop-filter: blur(15px);
|
||||
border: 1px solid rgba(255, 234, 167, 0.5);
|
||||
}
|
||||
|
||||
/* 错误区域背景 */
|
||||
.error-content {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
backdrop-filter: blur(20px);
|
||||
border: 1px solid rgba(220, 53, 69, 0.2);
|
||||
}
|
||||
|
||||
/* 输入框背景 */
|
||||
.form-input,
|
||||
.form-select {
|
||||
background: rgba(248, 255, 248, 0.9);
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.form-input:focus,
|
||||
.form-select:focus {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
backdrop-filter: blur(15px);
|
||||
}
|
||||
|
||||
/* 信息项背景 */
|
||||
.info-item {
|
||||
background: rgba(248, 255, 248, 0.8);
|
||||
backdrop-filter: blur(5px);
|
||||
}
|
||||
|
||||
/* BMI分类背景 */
|
||||
.bmi-category {
|
||||
background: rgba(232, 245, 232, 0.9);
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
/* 健康建议列表项背景 */
|
||||
.health-tips li {
|
||||
background: rgba(248, 255, 248, 0.8);
|
||||
backdrop-filter: blur(5px);
|
||||
}
|
||||
|
||||
/* 按钮背景增强 */
|
||||
.submit-btn {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.submit-btn::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
|
||||
transition: left 0.5s;
|
||||
}
|
||||
|
||||
.submit-btn:hover::before {
|
||||
left: 100%;
|
||||
}
|
||||
|
||||
/* 重置按钮背景 */
|
||||
.reset-btn {
|
||||
background: rgba(232, 245, 232, 0.9);
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.reset-btn:hover {
|
||||
background: rgba(212, 237, 218, 0.95);
|
||||
}
|
||||
|
||||
/* 响应式背景调整 */
|
||||
@media (max-width: 767px) {
|
||||
body::after {
|
||||
background-size: 150px 75px;
|
||||
animation-duration: 15s;
|
||||
}
|
||||
|
||||
.form-section::before {
|
||||
animation-duration: 2s;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 768px) and (max-width: 1024px) {
|
||||
body::after {
|
||||
background-size: 180px 90px;
|
||||
animation-duration: 18s;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
body::after {
|
||||
background-size: 220px 110px;
|
||||
animation-duration: 25s;
|
||||
}
|
||||
|
||||
.container {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
}
|
||||
@@ -1,115 +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="styles.css">
|
||||
<link rel="stylesheet" href="background.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<header class="header">
|
||||
<h1 class="title">身体健康分析</h1>
|
||||
<p class="subtitle">通过身高、体重、年龄、性别多维度分析身体健康状态</p>
|
||||
</header>
|
||||
|
||||
<main class="main-content">
|
||||
<div class="form-section">
|
||||
<form id="healthForm" class="health-form">
|
||||
<div class="form-group">
|
||||
<label for="height" class="form-label">身高 (cm)</label>
|
||||
<input type="number" id="height" name="height" class="form-input" placeholder="请输入身高" min="100" max="250" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="weight" class="form-label">体重 (kg)</label>
|
||||
<input type="number" id="weight" name="weight" class="form-input" placeholder="请输入体重" min="30" max="200" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="age" class="form-label">年龄</label>
|
||||
<input type="number" id="age" name="age" class="form-input" placeholder="请输入年龄" min="1" max="120" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="gender" class="form-label">性别</label>
|
||||
<select id="gender" name="gender" class="form-select" required>
|
||||
<option value="">请选择性别</option>
|
||||
<option value="male">男性</option>
|
||||
<option value="female">女性</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="submit-btn" id="analyzeBtn">
|
||||
<span class="btn-text">开始分析</span>
|
||||
<div class="loading-spinner" style="display: none;"></div>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="result-section" id="resultSection" style="display: none;">
|
||||
<div class="result-header">
|
||||
<h2 class="result-title">分析结果</h2>
|
||||
<button class="reset-btn" id="resetBtn">重新分析</button>
|
||||
</div>
|
||||
|
||||
<div class="result-content">
|
||||
<div class="basic-info-card">
|
||||
<h3 class="card-title">基本信息</h3>
|
||||
<div class="info-grid" id="basicInfo"></div>
|
||||
</div>
|
||||
|
||||
<div class="bmi-card">
|
||||
<h3 class="card-title">BMI 分析</h3>
|
||||
<div class="bmi-content" id="bmiContent"></div>
|
||||
</div>
|
||||
|
||||
<div class="weight-card">
|
||||
<h3 class="card-title">体重评估</h3>
|
||||
<div class="weight-content" id="weightContent"></div>
|
||||
</div>
|
||||
|
||||
<div class="metabolism-card">
|
||||
<h3 class="card-title">代谢分析</h3>
|
||||
<div class="metabolism-content" id="metabolismContent"></div>
|
||||
</div>
|
||||
|
||||
<div class="body-fat-card">
|
||||
<h3 class="card-title">体脂分析</h3>
|
||||
<div class="body-fat-content" id="bodyFatContent"></div>
|
||||
</div>
|
||||
|
||||
<div class="measurements-card">
|
||||
<h3 class="card-title">理想三围</h3>
|
||||
<div class="measurements-content" id="measurementsContent"></div>
|
||||
</div>
|
||||
|
||||
<div class="advice-card">
|
||||
<h3 class="card-title">健康建议</h3>
|
||||
<div class="advice-content" id="adviceContent"></div>
|
||||
</div>
|
||||
|
||||
<div class="disclaimer-card">
|
||||
<p class="disclaimer" id="disclaimer"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="error-section" id="errorSection" style="display: none;">
|
||||
<div class="error-content">
|
||||
<h3 class="error-title">分析失败</h3>
|
||||
<p class="error-message" id="errorMessage"></p>
|
||||
<button class="retry-btn" id="retryBtn">重试</button>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="footer">
|
||||
<p class="footer-text">数据来源:60s API</p>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,515 +0,0 @@
|
||||
// 身体健康分析 JavaScript 功能
|
||||
|
||||
// DOM 元素获取
|
||||
const healthForm = document.getElementById('healthForm');
|
||||
const analyzeBtn = document.getElementById('analyzeBtn');
|
||||
const btnText = analyzeBtn.querySelector('.btn-text');
|
||||
const loadingSpinner = analyzeBtn.querySelector('.loading-spinner');
|
||||
const resultSection = document.getElementById('resultSection');
|
||||
const errorSection = document.getElementById('errorSection');
|
||||
const resetBtn = document.getElementById('resetBtn');
|
||||
const retryBtn = document.getElementById('retryBtn');
|
||||
|
||||
// API 配置
|
||||
const API_BASE_URL = 'https://60s.api.shumengya.top/v2/health';
|
||||
|
||||
// 表单验证规则
|
||||
const validationRules = {
|
||||
height: {
|
||||
min: 100,
|
||||
max: 250,
|
||||
message: '身高应在100-250cm之间'
|
||||
},
|
||||
weight: {
|
||||
min: 30,
|
||||
max: 200,
|
||||
message: '体重应在30-200kg之间'
|
||||
},
|
||||
age: {
|
||||
min: 1,
|
||||
max: 120,
|
||||
message: '年龄应在1-120岁之间'
|
||||
}
|
||||
};
|
||||
|
||||
// 初始化
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
initializeEventListeners();
|
||||
setupFormValidation();
|
||||
});
|
||||
|
||||
// 事件监听器初始化
|
||||
function initializeEventListeners() {
|
||||
healthForm.addEventListener('submit', handleFormSubmit);
|
||||
resetBtn.addEventListener('click', resetForm);
|
||||
retryBtn.addEventListener('click', retryAnalysis);
|
||||
|
||||
// 输入框实时验证
|
||||
const inputs = healthForm.querySelectorAll('input, select');
|
||||
inputs.forEach(input => {
|
||||
input.addEventListener('blur', validateField);
|
||||
input.addEventListener('input', clearFieldError);
|
||||
});
|
||||
}
|
||||
|
||||
// 表单验证设置
|
||||
function setupFormValidation() {
|
||||
const inputs = healthForm.querySelectorAll('input[type="number"]');
|
||||
inputs.forEach(input => {
|
||||
input.addEventListener('input', function() {
|
||||
// 移除非数字字符
|
||||
this.value = this.value.replace(/[^0-9.]/g, '');
|
||||
|
||||
// 防止多个小数点
|
||||
const parts = this.value.split('.');
|
||||
if (parts.length > 2) {
|
||||
this.value = parts[0] + '.' + parts.slice(1).join('');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 表单提交处理
|
||||
async function handleFormSubmit(event) {
|
||||
event.preventDefault();
|
||||
|
||||
if (!validateForm()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = getFormData();
|
||||
|
||||
try {
|
||||
setLoadingState(true);
|
||||
hideAllSections();
|
||||
|
||||
const result = await callHealthAPI(formData);
|
||||
displayResults(result);
|
||||
|
||||
} catch (error) {
|
||||
console.error('分析失败:', error);
|
||||
displayError(error.message || '分析失败,请稍后重试');
|
||||
} finally {
|
||||
setLoadingState(false);
|
||||
}
|
||||
}
|
||||
|
||||
// 获取表单数据
|
||||
function getFormData() {
|
||||
return {
|
||||
height: parseInt(document.getElementById('height').value),
|
||||
weight: parseInt(document.getElementById('weight').value),
|
||||
age: parseInt(document.getElementById('age').value),
|
||||
gender: document.getElementById('gender').value
|
||||
};
|
||||
}
|
||||
|
||||
// 表单验证
|
||||
function validateForm() {
|
||||
let isValid = true;
|
||||
const inputs = healthForm.querySelectorAll('input, select');
|
||||
|
||||
inputs.forEach(input => {
|
||||
if (!validateField({ target: input })) {
|
||||
isValid = false;
|
||||
}
|
||||
});
|
||||
|
||||
return isValid;
|
||||
}
|
||||
|
||||
// 单个字段验证
|
||||
function validateField(event) {
|
||||
const field = event.target;
|
||||
const value = field.value.trim();
|
||||
const fieldName = field.name;
|
||||
|
||||
// 清除之前的错误状态
|
||||
clearFieldError(event);
|
||||
|
||||
// 必填验证
|
||||
if (!value) {
|
||||
showFieldError(field, '此字段为必填项');
|
||||
return false;
|
||||
}
|
||||
|
||||
// 数值范围验证
|
||||
if (validationRules[fieldName]) {
|
||||
const numValue = parseFloat(value);
|
||||
const rule = validationRules[fieldName];
|
||||
|
||||
if (numValue < rule.min || numValue > rule.max) {
|
||||
showFieldError(field, rule.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// 显示字段错误
|
||||
function showFieldError(field, message) {
|
||||
field.classList.add('error');
|
||||
|
||||
// 移除已存在的错误消息
|
||||
const existingError = field.parentNode.querySelector('.error-message');
|
||||
if (existingError) {
|
||||
existingError.remove();
|
||||
}
|
||||
|
||||
// 添加错误消息
|
||||
const errorDiv = document.createElement('div');
|
||||
errorDiv.className = 'error-message';
|
||||
errorDiv.textContent = message;
|
||||
errorDiv.style.color = '#dc3545';
|
||||
errorDiv.style.fontSize = '0.875rem';
|
||||
errorDiv.style.marginTop = '5px';
|
||||
|
||||
field.parentNode.appendChild(errorDiv);
|
||||
}
|
||||
|
||||
// 清除字段错误
|
||||
function clearFieldError(event) {
|
||||
const field = event.target;
|
||||
field.classList.remove('error');
|
||||
|
||||
const errorMessage = field.parentNode.querySelector('.error-message');
|
||||
if (errorMessage) {
|
||||
errorMessage.remove();
|
||||
}
|
||||
}
|
||||
|
||||
// 调用健康分析API
|
||||
async function callHealthAPI(data) {
|
||||
const params = new URLSearchParams({
|
||||
height: data.height,
|
||||
weight: data.weight,
|
||||
age: data.age,
|
||||
gender: data.gender
|
||||
});
|
||||
|
||||
const response = await fetch(`${API_BASE_URL}?${params}`);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.code !== 200) {
|
||||
throw new Error(result.message || '分析失败');
|
||||
}
|
||||
|
||||
return result.data;
|
||||
}
|
||||
|
||||
// 显示分析结果
|
||||
function displayResults(data) {
|
||||
// 基本信息
|
||||
displayBasicInfo(data.basic_info);
|
||||
|
||||
// BMI 分析
|
||||
displayBMIInfo(data.bmi);
|
||||
|
||||
// 体重评估
|
||||
displayWeightAssessment(data.weight_assessment);
|
||||
|
||||
// 代谢分析
|
||||
displayMetabolism(data.metabolism);
|
||||
|
||||
// 体脂分析
|
||||
displayBodyFat(data.body_fat);
|
||||
|
||||
// 理想三围
|
||||
displayMeasurements(data.ideal_measurements);
|
||||
|
||||
// 健康建议
|
||||
displayHealthAdvice(data.health_advice);
|
||||
|
||||
// 免责声明
|
||||
displayDisclaimer(data.disclaimer);
|
||||
|
||||
// 显示结果区域
|
||||
resultSection.style.display = 'block';
|
||||
resultSection.scrollIntoView({ behavior: 'smooth' });
|
||||
}
|
||||
|
||||
// 显示基本信息
|
||||
function displayBasicInfo(basicInfo) {
|
||||
const container = document.getElementById('basicInfo');
|
||||
container.innerHTML = '';
|
||||
|
||||
const infoItems = [
|
||||
{ label: basicInfo.height_desc, value: basicInfo.height },
|
||||
{ label: basicInfo.weight_desc, value: basicInfo.weight },
|
||||
{ label: basicInfo.age_desc, value: basicInfo.age },
|
||||
{ label: basicInfo.gender_desc, value: basicInfo.gender }
|
||||
];
|
||||
|
||||
infoItems.forEach(item => {
|
||||
const itemDiv = createInfoItem(item.label, item.value);
|
||||
container.appendChild(itemDiv);
|
||||
});
|
||||
}
|
||||
|
||||
// 显示BMI信息
|
||||
function displayBMIInfo(bmiData) {
|
||||
const container = document.getElementById('bmiContent');
|
||||
container.innerHTML = `
|
||||
<div class="bmi-value">${bmiData.value}</div>
|
||||
<div class="bmi-category">${bmiData.category}</div>
|
||||
<div class="info-grid">
|
||||
${createInfoItem(bmiData.evaluation_desc, bmiData.evaluation).outerHTML}
|
||||
${createInfoItem(bmiData.risk_desc, bmiData.risk).outerHTML}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// 显示体重评估
|
||||
function displayWeightAssessment(weightData) {
|
||||
const container = document.getElementById('weightContent');
|
||||
container.innerHTML = '';
|
||||
|
||||
const items = [
|
||||
{ label: weightData.ideal_weight_range_desc, value: weightData.ideal_weight_range },
|
||||
{ label: weightData.standard_weight_desc, value: weightData.standard_weight },
|
||||
{ label: weightData.status_desc, value: weightData.status },
|
||||
{ label: weightData.adjustment_desc, value: weightData.adjustment }
|
||||
];
|
||||
|
||||
const grid = document.createElement('div');
|
||||
grid.className = 'info-grid';
|
||||
|
||||
items.forEach(item => {
|
||||
const itemDiv = createInfoItem(item.label, item.value);
|
||||
grid.appendChild(itemDiv);
|
||||
});
|
||||
|
||||
container.appendChild(grid);
|
||||
}
|
||||
|
||||
// 显示代谢分析
|
||||
function displayMetabolism(metabolismData) {
|
||||
const container = document.getElementById('metabolismContent');
|
||||
container.innerHTML = '';
|
||||
|
||||
const items = [
|
||||
{ label: metabolismData.bmr_desc, value: metabolismData.bmr },
|
||||
{ label: metabolismData.tdee_desc, value: metabolismData.tdee },
|
||||
{ label: metabolismData.recommended_calories_desc, value: metabolismData.recommended_calories },
|
||||
{ label: metabolismData.weight_loss_calories_desc, value: metabolismData.weight_loss_calories },
|
||||
{ label: metabolismData.weight_gain_calories_desc, value: metabolismData.weight_gain_calories }
|
||||
];
|
||||
|
||||
const grid = document.createElement('div');
|
||||
grid.className = 'info-grid';
|
||||
|
||||
items.forEach(item => {
|
||||
const itemDiv = createInfoItem(item.label, item.value);
|
||||
grid.appendChild(itemDiv);
|
||||
});
|
||||
|
||||
container.appendChild(grid);
|
||||
}
|
||||
|
||||
// 显示体脂分析
|
||||
function displayBodyFat(bodyFatData) {
|
||||
const container = document.getElementById('bodyFatContent');
|
||||
container.innerHTML = '';
|
||||
|
||||
const items = [
|
||||
{ label: bodyFatData.percentage_desc, value: bodyFatData.percentage },
|
||||
{ label: bodyFatData.category_desc, value: bodyFatData.category },
|
||||
{ label: bodyFatData.fat_weight_desc, value: bodyFatData.fat_weight },
|
||||
{ label: bodyFatData.lean_weight_desc, value: bodyFatData.lean_weight }
|
||||
];
|
||||
|
||||
const grid = document.createElement('div');
|
||||
grid.className = 'info-grid';
|
||||
|
||||
items.forEach(item => {
|
||||
const itemDiv = createInfoItem(item.label, item.value);
|
||||
grid.appendChild(itemDiv);
|
||||
});
|
||||
|
||||
container.appendChild(grid);
|
||||
}
|
||||
|
||||
// 显示理想三围
|
||||
function displayMeasurements(measurementsData) {
|
||||
const container = document.getElementById('measurementsContent');
|
||||
container.innerHTML = '';
|
||||
|
||||
const items = [
|
||||
{ label: measurementsData.chest_desc, value: measurementsData.chest },
|
||||
{ label: measurementsData.waist_desc, value: measurementsData.waist },
|
||||
{ label: measurementsData.hip_desc, value: measurementsData.hip }
|
||||
];
|
||||
|
||||
const grid = document.createElement('div');
|
||||
grid.className = 'info-grid';
|
||||
|
||||
items.forEach(item => {
|
||||
const itemDiv = createInfoItem(item.label, item.value);
|
||||
grid.appendChild(itemDiv);
|
||||
});
|
||||
|
||||
// 添加说明
|
||||
const note = document.createElement('p');
|
||||
note.style.marginTop = '15px';
|
||||
note.style.fontSize = '0.9rem';
|
||||
note.style.color = '#4a7c59';
|
||||
note.style.textAlign = 'center';
|
||||
note.textContent = measurementsData.note;
|
||||
|
||||
container.appendChild(grid);
|
||||
container.appendChild(note);
|
||||
}
|
||||
|
||||
// 显示健康建议
|
||||
function displayHealthAdvice(adviceData) {
|
||||
const container = document.getElementById('adviceContent');
|
||||
container.innerHTML = '';
|
||||
|
||||
// 饮水量建议
|
||||
const waterDiv = createAdviceSection(adviceData.daily_water_intake_desc, adviceData.daily_water_intake);
|
||||
container.appendChild(waterDiv);
|
||||
|
||||
// 运动建议
|
||||
const exerciseDiv = createAdviceSection(adviceData.exercise_recommendation_desc, adviceData.exercise_recommendation);
|
||||
container.appendChild(exerciseDiv);
|
||||
|
||||
// 营养建议
|
||||
const nutritionDiv = createAdviceSection(adviceData.nutrition_advice_desc, adviceData.nutrition_advice);
|
||||
container.appendChild(nutritionDiv);
|
||||
|
||||
// 健康提示
|
||||
const tipsDiv = document.createElement('div');
|
||||
tipsDiv.innerHTML = `
|
||||
<h4 style="color: #2d5a3d; margin-bottom: 10px;">${adviceData.health_tips_desc}</h4>
|
||||
<ul class="health-tips"></ul>
|
||||
`;
|
||||
|
||||
const tipsList = tipsDiv.querySelector('.health-tips');
|
||||
adviceData.health_tips.forEach(tip => {
|
||||
const li = document.createElement('li');
|
||||
li.textContent = tip;
|
||||
tipsList.appendChild(li);
|
||||
});
|
||||
|
||||
container.appendChild(tipsDiv);
|
||||
}
|
||||
|
||||
// 创建建议区块
|
||||
function createAdviceSection(title, content) {
|
||||
const div = document.createElement('div');
|
||||
div.style.marginBottom = '20px';
|
||||
div.innerHTML = `
|
||||
<h4 style="color: #2d5a3d; margin-bottom: 8px;">${title}</h4>
|
||||
<p style="background: #f8fff8; padding: 12px; border-radius: 8px; border-left: 4px solid #4caf50; line-height: 1.6;">${content}</p>
|
||||
`;
|
||||
return div;
|
||||
}
|
||||
|
||||
// 显示免责声明
|
||||
function displayDisclaimer(disclaimer) {
|
||||
const container = document.getElementById('disclaimer');
|
||||
container.textContent = disclaimer;
|
||||
}
|
||||
|
||||
// 创建信息项
|
||||
function createInfoItem(label, value) {
|
||||
const div = document.createElement('div');
|
||||
div.className = 'info-item';
|
||||
div.innerHTML = `
|
||||
<div class="info-label">${label}</div>
|
||||
<div class="info-value">${value}</div>
|
||||
`;
|
||||
return div;
|
||||
}
|
||||
|
||||
// 显示错误信息
|
||||
function displayError(message) {
|
||||
const errorMessage = document.getElementById('errorMessage');
|
||||
errorMessage.textContent = message;
|
||||
errorSection.style.display = 'block';
|
||||
errorSection.scrollIntoView({ behavior: 'smooth' });
|
||||
}
|
||||
|
||||
// 设置加载状态
|
||||
function setLoadingState(isLoading) {
|
||||
if (isLoading) {
|
||||
analyzeBtn.disabled = true;
|
||||
btnText.style.display = 'none';
|
||||
loadingSpinner.style.display = 'block';
|
||||
} else {
|
||||
analyzeBtn.disabled = false;
|
||||
btnText.style.display = 'block';
|
||||
loadingSpinner.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// 隐藏所有结果区域
|
||||
function hideAllSections() {
|
||||
resultSection.style.display = 'none';
|
||||
errorSection.style.display = 'none';
|
||||
}
|
||||
|
||||
// 重置表单
|
||||
function resetForm() {
|
||||
healthForm.reset();
|
||||
hideAllSections();
|
||||
|
||||
// 清除所有错误状态
|
||||
const errorInputs = healthForm.querySelectorAll('.error');
|
||||
errorInputs.forEach(input => {
|
||||
input.classList.remove('error');
|
||||
});
|
||||
|
||||
const errorMessages = healthForm.querySelectorAll('.error-message');
|
||||
errorMessages.forEach(msg => msg.remove());
|
||||
|
||||
// 滚动到表单顶部
|
||||
healthForm.scrollIntoView({ behavior: 'smooth' });
|
||||
}
|
||||
|
||||
// 重试分析
|
||||
function retryAnalysis() {
|
||||
hideAllSections();
|
||||
healthForm.scrollIntoView({ behavior: 'smooth' });
|
||||
}
|
||||
|
||||
// 工具函数:防抖
|
||||
function debounce(func, wait) {
|
||||
let timeout;
|
||||
return function executedFunction(...args) {
|
||||
const later = () => {
|
||||
clearTimeout(timeout);
|
||||
func(...args);
|
||||
};
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(later, wait);
|
||||
};
|
||||
}
|
||||
|
||||
// 添加CSS样式到错误输入框
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
.form-input.error,
|
||||
.form-select.error {
|
||||
border-color: #dc3545 !important;
|
||||
box-shadow: 0 0 0 3px rgba(220, 53, 69, 0.1) !important;
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
|
||||
// 页面可见性变化处理(用户切换标签页时暂停动画等)
|
||||
document.addEventListener('visibilitychange', function() {
|
||||
if (document.hidden) {
|
||||
// 页面隐藏时的处理
|
||||
document.body.style.animationPlayState = 'paused';
|
||||
} else {
|
||||
// 页面显示时的处理
|
||||
document.body.style.animationPlayState = 'running';
|
||||
}
|
||||
});
|
||||
@@ -1,697 +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: #2d5a3d;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* 容器布局 */
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 头部样式 */
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 2.5rem;
|
||||
font-weight: 700;
|
||||
color: #1a4d2e;
|
||||
margin-bottom: 10px;
|
||||
text-shadow: 0 2px 4px rgba(26, 77, 46, 0.1);
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 1.1rem;
|
||||
color: #4a7c59;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
/* 主内容区域 */
|
||||
.main-content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 30px;
|
||||
}
|
||||
|
||||
/* 表单区域 */
|
||||
.form-section {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
border-radius: 20px;
|
||||
padding: 30px;
|
||||
box-shadow: 0 8px 32px rgba(26, 77, 46, 0.1);
|
||||
border: 1px solid rgba(144, 238, 144, 0.3);
|
||||
}
|
||||
|
||||
.health-form {
|
||||
display: grid;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
font-weight: 600;
|
||||
color: #2d5a3d;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.form-input,
|
||||
.form-select {
|
||||
padding: 15px 20px;
|
||||
border: 2px solid #a8e6a3;
|
||||
border-radius: 12px;
|
||||
font-size: 1rem;
|
||||
background: #f8fff8;
|
||||
color: #2d5a3d;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.form-input:focus,
|
||||
.form-select:focus {
|
||||
outline: none;
|
||||
border-color: #4caf50;
|
||||
box-shadow: 0 0 0 3px rgba(76, 175, 80, 0.1);
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.form-input::placeholder {
|
||||
color: #81c784;
|
||||
}
|
||||
|
||||
/* 提交按钮 */
|
||||
.submit-btn {
|
||||
background: linear-gradient(135deg, #4caf50, #66bb6a);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 18px 30px;
|
||||
border-radius: 12px;
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.submit-btn:hover {
|
||||
background: linear-gradient(135deg, #45a049, #5cb85c);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 25px rgba(76, 175, 80, 0.3);
|
||||
}
|
||||
|
||||
.submit-btn:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.submit-btn:disabled {
|
||||
background: #c8e6c9;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/* 加载动画 */
|
||||
.loading-spinner {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 2px solid rgba(255, 255, 255, 0.3);
|
||||
border-top: 2px solid white;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* 结果区域 */
|
||||
.result-section {
|
||||
animation: fadeInUp 0.6s ease-out;
|
||||
}
|
||||
|
||||
.result-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 25px;
|
||||
flex-wrap: wrap;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.result-title {
|
||||
font-size: 2rem;
|
||||
color: #1a4d2e;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.reset-btn {
|
||||
background: #e8f5e8;
|
||||
color: #2d5a3d;
|
||||
border: 2px solid #a8e6a3;
|
||||
padding: 10px 20px;
|
||||
border-radius: 8px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.reset-btn:hover {
|
||||
background: #d4edda;
|
||||
border-color: #4caf50;
|
||||
}
|
||||
|
||||
/* 结果卡片 */
|
||||
.result-content {
|
||||
display: grid;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.basic-info-card,
|
||||
.bmi-card,
|
||||
.weight-card,
|
||||
.metabolism-card,
|
||||
.body-fat-card,
|
||||
.measurements-card,
|
||||
.advice-card,
|
||||
.disclaimer-card {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
border-radius: 16px;
|
||||
padding: 25px;
|
||||
box-shadow: 0 6px 20px rgba(26, 77, 46, 0.08);
|
||||
border: 1px solid rgba(144, 238, 144, 0.2);
|
||||
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
||||
}
|
||||
|
||||
.basic-info-card:hover,
|
||||
.bmi-card:hover,
|
||||
.weight-card:hover,
|
||||
.metabolism-card:hover,
|
||||
.body-fat-card:hover,
|
||||
.measurements-card:hover,
|
||||
.advice-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 25px rgba(26, 77, 46, 0.12);
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 1.4rem;
|
||||
color: #1a4d2e;
|
||||
font-weight: 700;
|
||||
margin-bottom: 15px;
|
||||
border-bottom: 2px solid #e8f5e8;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
/* 信息网格 */
|
||||
.info-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
background: #f8fff8;
|
||||
padding: 15px;
|
||||
border-radius: 10px;
|
||||
border-left: 4px solid #4caf50;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
font-size: 0.9rem;
|
||||
color: #4a7c59;
|
||||
font-weight: 600;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
font-size: 1.2rem;
|
||||
color: #2d5a3d;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
/* BMI 特殊样式 */
|
||||
.bmi-value {
|
||||
font-size: 2.5rem;
|
||||
font-weight: 800;
|
||||
color: #4caf50;
|
||||
text-align: center;
|
||||
margin: 15px 0;
|
||||
}
|
||||
|
||||
.bmi-category {
|
||||
text-align: center;
|
||||
font-size: 1.3rem;
|
||||
font-weight: 600;
|
||||
color: #2d5a3d;
|
||||
background: #e8f5e8;
|
||||
padding: 10px;
|
||||
border-radius: 8px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
/* 健康建议列表 */
|
||||
.health-tips {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.health-tips li {
|
||||
background: #f8fff8;
|
||||
margin: 10px 0;
|
||||
padding: 12px 15px;
|
||||
border-radius: 8px;
|
||||
border-left: 4px solid #81c784;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.health-tips li::before {
|
||||
content: "✓";
|
||||
color: #4caf50;
|
||||
font-weight: bold;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
/* 免责声明 */
|
||||
.disclaimer {
|
||||
background: #fff3cd;
|
||||
border: 1px solid #ffeaa7;
|
||||
color: #856404;
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
font-size: 0.95rem;
|
||||
line-height: 1.5;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* 错误区域 */
|
||||
.error-section {
|
||||
text-align: center;
|
||||
padding: 40px 20px;
|
||||
}
|
||||
|
||||
.error-content {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
border-radius: 16px;
|
||||
padding: 30px;
|
||||
box-shadow: 0 6px 20px rgba(220, 53, 69, 0.1);
|
||||
border: 1px solid rgba(220, 53, 69, 0.2);
|
||||
max-width: 400px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.error-title {
|
||||
color: #dc3545;
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: #6c757d;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.retry-btn {
|
||||
background: #dc3545;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 12px 24px;
|
||||
border-radius: 8px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: background 0.3s ease;
|
||||
}
|
||||
|
||||
.retry-btn:hover {
|
||||
background: #c82333;
|
||||
}
|
||||
|
||||
/* 底部 */
|
||||
.footer {
|
||||
text-align: center;
|
||||
padding: 20px 0;
|
||||
margin-top: 30px;
|
||||
border-top: 1px solid rgba(144, 238, 144, 0.3);
|
||||
}
|
||||
|
||||
.footer-text {
|
||||
color: #4a7c59;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
/* 动画效果 */
|
||||
@keyframes fadeInUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* 平板端适配 (768px - 1024px) */
|
||||
@media (min-width: 768px) and (max-width: 1024px) {
|
||||
.container {
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 2.8rem;
|
||||
}
|
||||
|
||||
.form-section {
|
||||
padding: 35px;
|
||||
}
|
||||
|
||||
.health-form {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 25px;
|
||||
}
|
||||
|
||||
.form-group:last-child {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.result-content {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.advice-card,
|
||||
.disclaimer-card {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
}
|
||||
|
||||
/* 电脑端适配 (1024px+) */
|
||||
@media (min-width: 1024px) {
|
||||
.container {
|
||||
padding: 40px;
|
||||
max-width: 1400px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 3.2rem;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
flex-direction: row;
|
||||
gap: 40px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.form-section {
|
||||
flex: 0 0 380px;
|
||||
position: sticky;
|
||||
top: 20px;
|
||||
max-height: calc(100vh - 40px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.result-section,
|
||||
.error-section {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
/* 桌面端结果区域重新设计 - 使用更清晰的布局 */
|
||||
.result-content {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 25px;
|
||||
grid-auto-rows: min-content;
|
||||
}
|
||||
|
||||
/* 基本信息卡片 - 占满第一行 */
|
||||
.basic-info-card {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
/* 第二行:BMI、体重评估、代谢分析 */
|
||||
.bmi-card,
|
||||
.weight-card,
|
||||
.metabolism-card {
|
||||
grid-column: span 1;
|
||||
}
|
||||
|
||||
/* 第三行:体脂分析和理想三围 */
|
||||
.body-fat-card {
|
||||
grid-column: span 2;
|
||||
}
|
||||
|
||||
.measurements-card {
|
||||
grid-column: span 1;
|
||||
}
|
||||
|
||||
/* 第四行:健康建议 - 占满整行 */
|
||||
.advice-card {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
/* 第五行:免责声明 - 占满整行 */
|
||||
.disclaimer-card {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
/* 基本信息网格优化 */
|
||||
.basic-info-card .info-grid {
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
/* BMI卡片特殊布局 */
|
||||
.bmi-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 280px;
|
||||
}
|
||||
|
||||
.bmi-card .bmi-content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.bmi-value {
|
||||
font-size: 3rem;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
/* 体重评估卡片布局优化 */
|
||||
.weight-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 280px;
|
||||
}
|
||||
|
||||
.weight-card .weight-content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.weight-card .info-grid {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
/* 代谢分析卡片布局优化 */
|
||||
.metabolism-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 280px;
|
||||
}
|
||||
|
||||
.metabolism-card .metabolism-content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.metabolism-card .info-grid {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
/* 体脂分析卡片网格优化 */
|
||||
.body-fat-card .info-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
/* 理想三围卡片网格优化 */
|
||||
.measurements-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
.measurements-card .measurements-content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.measurements-card .info-grid {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
/* 健康建议卡片布局优化 */
|
||||
.advice-card {
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
.advice-card .advice-content {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 25px;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.advice-card .health-tips {
|
||||
grid-column: 1 / -1;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 15px;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* 表单区域优化 */
|
||||
.health-form {
|
||||
display: grid;
|
||||
gap: 25px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
margin-top: 20px;
|
||||
padding: 20px 30px;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* 手机端适配 (最高优先级) */
|
||||
@media (max-width: 767px) {
|
||||
.container {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.header {
|
||||
margin-bottom: 20px;
|
||||
padding: 15px 0;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.form-section {
|
||||
padding: 20px;
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
||||
.form-input,
|
||||
.form-select {
|
||||
padding: 12px 16px;
|
||||
font-size: 16px; /* 防止iOS缩放 */
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
padding: 16px 24px;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.result-header {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.result-title {
|
||||
font-size: 1.6rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.reset-btn {
|
||||
align-self: center;
|
||||
padding: 12px 24px;
|
||||
}
|
||||
|
||||
.basic-info-card,
|
||||
.bmi-card,
|
||||
.weight-card,
|
||||
.metabolism-card,
|
||||
.body-fat-card,
|
||||
.measurements-card,
|
||||
.advice-card,
|
||||
.disclaimer-card {
|
||||
padding: 20px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.info-grid {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.bmi-value {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.bmi-category {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.health-tips li {
|
||||
padding: 10px 12px;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.error-content {
|
||||
padding: 25px 20px;
|
||||
}
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
{
|
||||
"code": 200,
|
||||
"message": "获取成功。数据来自官方/权威源头,以确保稳定与实时。开源地址 https://github.com/vikiboss/60s,反馈群 595941841",
|
||||
"data": {
|
||||
"basic_info": {
|
||||
"height": "176cm",
|
||||
"height_desc": "身高",
|
||||
"weight": "60kg",
|
||||
"weight_desc": "体重",
|
||||
"gender": "男性",
|
||||
"gender_desc": "性别",
|
||||
"age": "24岁",
|
||||
"age_desc": "年龄"
|
||||
},
|
||||
"bmi": {
|
||||
"value": 19.37,
|
||||
"value_desc": "BMI 值",
|
||||
"category": "正常体重",
|
||||
"category_desc": "BMI 分类",
|
||||
"evaluation": "体重正常,保持良好",
|
||||
"evaluation_desc": "BMI 评价",
|
||||
"risk": "健康风险较低",
|
||||
"risk_desc": "健康风险"
|
||||
},
|
||||
"weight_assessment": {
|
||||
"ideal_weight_range": "57.3-74.3kg",
|
||||
"ideal_weight_range_desc": "理想体重范围",
|
||||
"standard_weight": "71kg",
|
||||
"standard_weight_desc": "标准体重",
|
||||
"status": "体重正常",
|
||||
"status_desc": "体重状态",
|
||||
"adjustment": "保持当前体重",
|
||||
"adjustment_desc": "调整建议"
|
||||
},
|
||||
"metabolism": {
|
||||
"bmr": "1601 卡路里/天",
|
||||
"bmr_desc": "基础代谢率",
|
||||
"tdee": "2561 卡路里/天",
|
||||
"tdee_desc": "每日总消耗",
|
||||
"recommended_calories": "2561 卡路里/天",
|
||||
"recommended_calories_desc": "推荐卡路里摄入",
|
||||
"weight_loss_calories": "2061 卡路里/天",
|
||||
"weight_loss_calories_desc": "减重卡路里",
|
||||
"weight_gain_calories": "2861 卡路里/天",
|
||||
"weight_gain_calories_desc": "增重卡路里"
|
||||
},
|
||||
"body_surface_area": {
|
||||
"value": "1.74m²",
|
||||
"value_desc": "体表面积",
|
||||
"formula": "Du Bois 公式",
|
||||
"formula_desc": "计算公式"
|
||||
},
|
||||
"body_fat": {
|
||||
"percentage": "12.6%",
|
||||
"percentage_desc": "体脂率",
|
||||
"category": "正常",
|
||||
"category_desc": "体脂分类",
|
||||
"fat_weight": "7.6kg",
|
||||
"fat_weight_desc": "脂肪重量",
|
||||
"lean_weight": "52.4kg",
|
||||
"lean_weight_desc": "瘦体重"
|
||||
},
|
||||
"health_advice": {
|
||||
"daily_water_intake": "2000ml (约 8 杯水),运动时需额外补充 500-1000ml",
|
||||
"daily_water_intake_desc": "每日饮水量",
|
||||
"exercise_recommendation": "继续保持运动习惯,有氧运动和力量训练相结合效果更佳。年轻人可选择多样化的运动方式,建议每周运动 3-5 次",
|
||||
"exercise_recommendation_desc": "运动建议",
|
||||
"nutrition_advice": "保持均衡饮食,三大营养素合理搭配,定时定量进餐。年轻人新陈代谢较快,可适当增加能量摄入,男性可适当增加蛋白质摄入",
|
||||
"nutrition_advice_desc": "营养建议",
|
||||
"health_tips": [
|
||||
"保持充足睡眠,成年人建议每天 7-9 小时",
|
||||
"定期体检有助于早期发现健康问题",
|
||||
"保持良好心态,适当释放压力",
|
||||
"年轻人要注意作息规律,合理安排工作与休息",
|
||||
"长时间用眼后适当休息,保护视力",
|
||||
"培养兴趣爱好,保持积极的生活态度",
|
||||
"多饮水,成年人每天 1500-2000ml 为宜"
|
||||
],
|
||||
"health_tips_desc": "健康提示"
|
||||
},
|
||||
"ideal_measurements": {
|
||||
"chest": "84cm",
|
||||
"chest_desc": "胸围",
|
||||
"waist": "74cm",
|
||||
"waist_desc": "腰围",
|
||||
"hip": "83cm",
|
||||
"hip_desc": "臀围",
|
||||
"note": "男性理想三围参考标准",
|
||||
"note_desc": "说明"
|
||||
},
|
||||
"disclaimer": "结果基于通用公式和统计数据,仅供参考,不能替代专业医疗建议。如有健康问题,请咨询医生。"
|
||||
}
|
||||
}
|
||||
@@ -1,187 +0,0 @@
|
||||
/* 背景样式文件 - 单独管理所有背景相关样式 */
|
||||
|
||||
/* 主体背景 */
|
||||
body {
|
||||
background: linear-gradient(135deg, #f0fff4 0%, #e6fffa 50%, #f0fff4 100%);
|
||||
background-attachment: fixed;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* 背景装饰元素 */
|
||||
body::before {
|
||||
content: '';
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image:
|
||||
radial-gradient(circle at 20% 80%, rgba(104, 211, 145, 0.1) 0%, transparent 50%),
|
||||
radial-gradient(circle at 80% 20%, rgba(72, 187, 120, 0.1) 0%, transparent 50%),
|
||||
radial-gradient(circle at 40% 40%, rgba(56, 161, 105, 0.05) 0%, transparent 50%);
|
||||
pointer-events: none;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
/* 容器背景 */
|
||||
.container {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
backdrop-filter: blur(10px);
|
||||
border-radius: 20px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
/* 输入区域背景 */
|
||||
.input-section {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
backdrop-filter: blur(20px);
|
||||
border: 1px solid rgba(104, 211, 145, 0.2);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.input-section::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 3px;
|
||||
background: linear-gradient(90deg, #48bb78, #68d391, #9ae6b4);
|
||||
}
|
||||
|
||||
/* 配色方案卡片背景 */
|
||||
.palette {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
backdrop-filter: blur(15px);
|
||||
border: 1px solid rgba(104, 211, 145, 0.15);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.palette::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 2px;
|
||||
background: linear-gradient(90deg, transparent, #68d391, transparent);
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.palette:hover::before {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* 颜色信息背景 */
|
||||
.color-info {
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(104, 211, 145, 0.2);
|
||||
}
|
||||
|
||||
/* 颜色项背景 */
|
||||
.color-item {
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
backdrop-filter: blur(5px);
|
||||
border: 1px solid rgba(104, 211, 145, 0.15);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.color-item::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: linear-gradient(45deg, transparent 48%, rgba(104, 211, 145, 0.05) 50%, transparent 52%);
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.color-item:hover::after {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* 颜色详情背景 */
|
||||
.color-detail {
|
||||
background: rgba(104, 211, 145, 0.08);
|
||||
border: 1px solid rgba(104, 211, 145, 0.1);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.color-detail::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: linear-gradient(135deg, rgba(255, 255, 255, 0.1) 0%, transparent 100%);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* 按钮背景 */
|
||||
.generate-btn {
|
||||
background: linear-gradient(135deg, #48bb78 0%, #68d391 50%, #9ae6b4 100%);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.generate-btn::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
|
||||
transition: left 0.5s ease;
|
||||
}
|
||||
|
||||
.generate-btn:hover::before {
|
||||
left: 100%;
|
||||
}
|
||||
|
||||
/* 加载动画背景 */
|
||||
.loading {
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
backdrop-filter: blur(10px);
|
||||
border-radius: 12px;
|
||||
border: 1px solid rgba(104, 211, 145, 0.2);
|
||||
}
|
||||
|
||||
/* 响应式背景调整 */
|
||||
@media (max-width: 768px) {
|
||||
body {
|
||||
background: linear-gradient(180deg, #f0fff4 0%, #e6fffa 100%);
|
||||
}
|
||||
|
||||
.container {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
backdrop-filter: blur(5px);
|
||||
}
|
||||
|
||||
.input-section,
|
||||
.palette,
|
||||
.color-info {
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
body::before {
|
||||
background-image:
|
||||
radial-gradient(circle at 50% 50%, rgba(104, 211, 145, 0.08) 0%, transparent 70%);
|
||||
}
|
||||
|
||||
.container {
|
||||
background: transparent;
|
||||
backdrop-filter: none;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
@@ -1,64 +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="styles.css">
|
||||
<link rel="stylesheet" href="background.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<header class="header">
|
||||
<h1>配色方案生成器</h1>
|
||||
<p class="subtitle">输入颜色值,获取专业的配色方案</p>
|
||||
</header>
|
||||
|
||||
<main class="main-content">
|
||||
<section class="input-section">
|
||||
<div class="color-input-group">
|
||||
<label for="colorInput">颜色值</label>
|
||||
<div class="input-wrapper">
|
||||
<input type="text" id="colorInput" placeholder="#33AAFF" value="#DE4F99">
|
||||
<input type="color" id="colorPicker" value="#DE4F99">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="format-select">
|
||||
<label for="formatSelect">输出格式</label>
|
||||
<select id="formatSelect">
|
||||
<option value="json">JSON</option>
|
||||
<option value="text">文本</option>
|
||||
<option value="html">HTML</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<button id="generateBtn" class="generate-btn">生成配色方案</button>
|
||||
</section>
|
||||
|
||||
<section class="result-section" id="resultSection">
|
||||
<div class="loading" id="loading" style="display: none;">
|
||||
<div class="spinner"></div>
|
||||
<p>正在生成配色方案...</p>
|
||||
</div>
|
||||
|
||||
<div class="color-info" id="colorInfo" style="display: none;">
|
||||
<h3>输入颜色信息</h3>
|
||||
<div class="color-preview" id="colorPreview"></div>
|
||||
<div class="color-details" id="colorDetails"></div>
|
||||
</div>
|
||||
|
||||
<div class="palettes-container" id="palettesContainer">
|
||||
<!-- 配色方案将在这里动态生成 -->
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer class="footer">
|
||||
<p>基于色彩理论的专业配色方案生成</p>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,315 +0,0 @@
|
||||
// 配色方案生成器 JavaScript
|
||||
class ColorPaletteGenerator {
|
||||
constructor() {
|
||||
this.apiUrl = 'https://60s.api.shumengya.top/v2/color/palette';
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
this.bindEvents();
|
||||
this.loadDefaultPalette();
|
||||
}
|
||||
|
||||
bindEvents() {
|
||||
const colorInput = document.getElementById('colorInput');
|
||||
const colorPicker = document.getElementById('colorPicker');
|
||||
const generateBtn = document.getElementById('generateBtn');
|
||||
const formatSelect = document.getElementById('formatSelect');
|
||||
|
||||
// 颜色输入框事件
|
||||
colorInput.addEventListener('input', (e) => {
|
||||
const color = e.target.value;
|
||||
if (this.isValidColor(color)) {
|
||||
colorPicker.value = color;
|
||||
}
|
||||
});
|
||||
|
||||
// 颜色选择器事件
|
||||
colorPicker.addEventListener('change', (e) => {
|
||||
colorInput.value = e.target.value;
|
||||
});
|
||||
|
||||
// 生成按钮事件
|
||||
generateBtn.addEventListener('click', () => {
|
||||
this.generatePalette();
|
||||
});
|
||||
|
||||
// 回车键生成
|
||||
colorInput.addEventListener('keypress', (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
this.generatePalette();
|
||||
}
|
||||
});
|
||||
|
||||
// 格式选择事件
|
||||
formatSelect.addEventListener('change', () => {
|
||||
const currentColor = colorInput.value;
|
||||
if (currentColor && this.isValidColor(currentColor)) {
|
||||
this.generatePalette();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 验证颜色格式
|
||||
isValidColor(color) {
|
||||
const hexRegex = /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/;
|
||||
return hexRegex.test(color);
|
||||
}
|
||||
|
||||
// 显示加载状态
|
||||
showLoading() {
|
||||
const loading = document.getElementById('loading');
|
||||
const colorInfo = document.getElementById('colorInfo');
|
||||
const palettesContainer = document.getElementById('palettesContainer');
|
||||
|
||||
loading.style.display = 'block';
|
||||
colorInfo.style.display = 'none';
|
||||
palettesContainer.innerHTML = '';
|
||||
}
|
||||
|
||||
// 隐藏加载状态
|
||||
hideLoading() {
|
||||
const loading = document.getElementById('loading');
|
||||
loading.style.display = 'none';
|
||||
}
|
||||
|
||||
// 生成配色方案
|
||||
async generatePalette() {
|
||||
const colorInput = document.getElementById('colorInput');
|
||||
const formatSelect = document.getElementById('formatSelect');
|
||||
const color = colorInput.value.trim();
|
||||
const format = formatSelect.value;
|
||||
|
||||
if (!color) {
|
||||
this.showError('请输入颜色值');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.isValidColor(color)) {
|
||||
this.showError('请输入有效的十六进制颜色值(如:#33AAFF)');
|
||||
return;
|
||||
}
|
||||
|
||||
this.showLoading();
|
||||
|
||||
try {
|
||||
const url = new URL(this.apiUrl);
|
||||
url.searchParams.append('color', color);
|
||||
url.searchParams.append('encoding', format);
|
||||
|
||||
const response = await fetch(url);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.code === 200) {
|
||||
this.displayResults(data.data);
|
||||
} else {
|
||||
throw new Error(data.message || '获取配色方案失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
this.showError('获取配色方案失败,请检查网络连接或稍后重试');
|
||||
} finally {
|
||||
this.hideLoading();
|
||||
}
|
||||
}
|
||||
|
||||
// 显示错误信息
|
||||
showError(message) {
|
||||
const palettesContainer = document.getElementById('palettesContainer');
|
||||
palettesContainer.innerHTML = `
|
||||
<div class="error-message" style="
|
||||
background: rgba(254, 226, 226, 0.9);
|
||||
border: 1px solid #feb2b2;
|
||||
color: #c53030;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
text-align: center;
|
||||
font-weight: 500;
|
||||
">
|
||||
<p>❌ ${message}</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// 显示结果
|
||||
displayResults(data) {
|
||||
this.displayColorInfo(data.input);
|
||||
this.displayPalettes(data.palettes);
|
||||
}
|
||||
|
||||
// 显示颜色信息
|
||||
displayColorInfo(inputData) {
|
||||
const colorInfo = document.getElementById('colorInfo');
|
||||
const colorPreview = document.getElementById('colorPreview');
|
||||
const colorDetails = document.getElementById('colorDetails');
|
||||
|
||||
colorPreview.style.backgroundColor = inputData.hex;
|
||||
|
||||
colorDetails.innerHTML = `
|
||||
<div class="color-detail">
|
||||
<strong>HEX</strong>
|
||||
<span>${inputData.hex}</span>
|
||||
</div>
|
||||
<div class="color-detail">
|
||||
<strong>RGB</strong>
|
||||
<span>rgb(${inputData.rgb.r}, ${inputData.rgb.g}, ${inputData.rgb.b})</span>
|
||||
</div>
|
||||
<div class="color-detail">
|
||||
<strong>HSL</strong>
|
||||
<span>hsl(${inputData.hsl.h}°, ${inputData.hsl.s}%, ${inputData.hsl.l}%)</span>
|
||||
</div>
|
||||
<div class="color-detail">
|
||||
<strong>色系</strong>
|
||||
<span>${inputData.name}</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
colorInfo.style.display = 'block';
|
||||
}
|
||||
|
||||
// 显示配色方案
|
||||
displayPalettes(palettes) {
|
||||
const palettesContainer = document.getElementById('palettesContainer');
|
||||
|
||||
palettesContainer.innerHTML = palettes.map(palette => `
|
||||
<div class="palette">
|
||||
<div class="palette-header">
|
||||
<h3 class="palette-name">${palette.name}</h3>
|
||||
<p class="palette-description">${palette.description}</p>
|
||||
</div>
|
||||
<div class="colors-grid">
|
||||
${palette.colors.map(color => `
|
||||
<div class="color-item">
|
||||
<div class="color-swatch"
|
||||
style="background-color: ${color.hex}"
|
||||
onclick="copyToClipboard('${color.hex}')"
|
||||
title="点击复制 ${color.hex}">
|
||||
</div>
|
||||
<div class="color-name">${color.name}</div>
|
||||
<div class="color-hex">${color.hex}</div>
|
||||
<div class="color-role">${color.role} • ${color.theory}</div>
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
// 加载默认配色方案
|
||||
async loadDefaultPalette() {
|
||||
const colorInput = document.getElementById('colorInput');
|
||||
const defaultColor = colorInput.value;
|
||||
|
||||
if (defaultColor && this.isValidColor(defaultColor)) {
|
||||
await this.generatePalette();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 复制到剪贴板功能
|
||||
function copyToClipboard(text) {
|
||||
if (navigator.clipboard && window.isSecureContext) {
|
||||
navigator.clipboard.writeText(text).then(() => {
|
||||
showToast(`已复制 ${text} 到剪贴板`);
|
||||
}).catch(err => {
|
||||
console.error('复制失败:', err);
|
||||
fallbackCopyTextToClipboard(text);
|
||||
});
|
||||
} else {
|
||||
fallbackCopyTextToClipboard(text);
|
||||
}
|
||||
}
|
||||
|
||||
// 备用复制方法
|
||||
function fallbackCopyTextToClipboard(text) {
|
||||
const textArea = document.createElement('textarea');
|
||||
textArea.value = text;
|
||||
textArea.style.position = 'fixed';
|
||||
textArea.style.left = '-999999px';
|
||||
textArea.style.top = '-999999px';
|
||||
document.body.appendChild(textArea);
|
||||
textArea.focus();
|
||||
textArea.select();
|
||||
|
||||
try {
|
||||
document.execCommand('copy');
|
||||
showToast(`已复制 ${text} 到剪贴板`);
|
||||
} catch (err) {
|
||||
console.error('复制失败:', err);
|
||||
showToast('复制失败,请手动复制');
|
||||
}
|
||||
|
||||
document.body.removeChild(textArea);
|
||||
}
|
||||
|
||||
// 显示提示信息
|
||||
function showToast(message) {
|
||||
// 移除已存在的toast
|
||||
const existingToast = document.querySelector('.toast');
|
||||
if (existingToast) {
|
||||
existingToast.remove();
|
||||
}
|
||||
|
||||
const toast = document.createElement('div');
|
||||
toast.className = 'toast';
|
||||
toast.textContent = message;
|
||||
toast.style.cssText = `
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
background: rgba(45, 90, 39, 0.95);
|
||||
color: white;
|
||||
padding: 12px 20px;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
z-index: 10000;
|
||||
box-shadow: 0 4px 12px rgba(45, 90, 39, 0.3);
|
||||
transform: translateX(100%);
|
||||
transition: transform 0.3s ease;
|
||||
backdrop-filter: blur(10px);
|
||||
`;
|
||||
|
||||
document.body.appendChild(toast);
|
||||
|
||||
// 动画显示
|
||||
setTimeout(() => {
|
||||
toast.style.transform = 'translateX(0)';
|
||||
}, 100);
|
||||
|
||||
// 3秒后隐藏
|
||||
setTimeout(() => {
|
||||
toast.style.transform = 'translateX(100%)';
|
||||
setTimeout(() => {
|
||||
if (toast.parentNode) {
|
||||
toast.parentNode.removeChild(toast);
|
||||
}
|
||||
}, 300);
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
// 页面加载完成后初始化
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
new ColorPaletteGenerator();
|
||||
});
|
||||
|
||||
// 添加移动端优化
|
||||
if ('ontouchstart' in window) {
|
||||
// 移动端触摸优化
|
||||
document.addEventListener('touchstart', function() {}, {passive: true});
|
||||
|
||||
// 防止双击缩放
|
||||
let lastTouchEnd = 0;
|
||||
document.addEventListener('touchend', function (event) {
|
||||
const now = (new Date()).getTime();
|
||||
if (now - lastTouchEnd <= 300) {
|
||||
event.preventDefault();
|
||||
}
|
||||
lastTouchEnd = now;
|
||||
}, false);
|
||||
}
|
||||
@@ -1,422 +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: #2d3748;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 头部样式 */
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-bottom: 40px;
|
||||
padding: 30px 0;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 2.5rem;
|
||||
color: #2d5a27;
|
||||
margin-bottom: 10px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 1.1rem;
|
||||
color: #68d391;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
/* 主内容区域 */
|
||||
.main-content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 30px;
|
||||
}
|
||||
|
||||
/* 输入区域 */
|
||||
.input-section {
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
padding: 30px;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 4px 20px rgba(45, 90, 39, 0.1);
|
||||
border: 1px solid rgba(104, 211, 145, 0.2);
|
||||
}
|
||||
|
||||
.color-input-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.color-input-group label,
|
||||
.format-select label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
font-weight: 600;
|
||||
color: #2d5a27;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.input-wrapper {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#colorInput {
|
||||
flex: 1;
|
||||
padding: 12px 16px;
|
||||
border: 2px solid #e2e8f0;
|
||||
border-radius: 8px;
|
||||
font-size: 1rem;
|
||||
transition: all 0.3s ease;
|
||||
background: white;
|
||||
}
|
||||
|
||||
#colorInput:focus {
|
||||
outline: none;
|
||||
border-color: #68d391;
|
||||
box-shadow: 0 0 0 3px rgba(104, 211, 145, 0.1);
|
||||
}
|
||||
|
||||
#colorPicker {
|
||||
width: 50px;
|
||||
height: 44px;
|
||||
border: 2px solid #e2e8f0;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
background: none;
|
||||
}
|
||||
|
||||
.format-select {
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
#formatSelect {
|
||||
width: 100%;
|
||||
padding: 12px 16px;
|
||||
border: 2px solid #e2e8f0;
|
||||
border-radius: 8px;
|
||||
font-size: 1rem;
|
||||
background: white;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
#formatSelect:focus {
|
||||
outline: none;
|
||||
border-color: #68d391;
|
||||
box-shadow: 0 0 0 3px rgba(104, 211, 145, 0.1);
|
||||
}
|
||||
|
||||
.generate-btn {
|
||||
width: 100%;
|
||||
padding: 14px 24px;
|
||||
background: linear-gradient(135deg, #48bb78, #68d391);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 4px 12px rgba(72, 187, 120, 0.3);
|
||||
}
|
||||
|
||||
.generate-btn:hover {
|
||||
background: linear-gradient(135deg, #38a169, #48bb78);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 20px rgba(72, 187, 120, 0.4);
|
||||
}
|
||||
|
||||
.generate-btn:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
/* 结果区域 */
|
||||
.result-section {
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
.loading {
|
||||
text-align: center;
|
||||
padding: 40px;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 4px solid #e2e8f0;
|
||||
border-top: 4px solid #68d391;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin: 0 auto 20px;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.loading p {
|
||||
color: #68d391;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 颜色信息 */
|
||||
.color-info {
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
padding: 25px;
|
||||
border-radius: 12px;
|
||||
margin-bottom: 25px;
|
||||
box-shadow: 0 2px 10px rgba(45, 90, 39, 0.1);
|
||||
border: 1px solid rgba(104, 211, 145, 0.2);
|
||||
}
|
||||
|
||||
.color-info h3 {
|
||||
color: #2d5a27;
|
||||
margin-bottom: 15px;
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
|
||||
.color-preview {
|
||||
width: 100%;
|
||||
height: 60px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 15px;
|
||||
border: 2px solid rgba(104, 211, 145, 0.3);
|
||||
}
|
||||
|
||||
.color-details {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.color-detail {
|
||||
text-align: center;
|
||||
padding: 10px;
|
||||
background: rgba(104, 211, 145, 0.1);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.color-detail strong {
|
||||
display: block;
|
||||
color: #2d5a27;
|
||||
font-size: 0.9rem;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.color-detail span {
|
||||
color: #4a5568;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
/* 配色方案容器 */
|
||||
.palettes-container {
|
||||
display: grid;
|
||||
gap: 25px;
|
||||
}
|
||||
|
||||
.palette {
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
border-radius: 12px;
|
||||
padding: 25px;
|
||||
box-shadow: 0 4px 15px rgba(45, 90, 39, 0.1);
|
||||
border: 1px solid rgba(104, 211, 145, 0.2);
|
||||
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
||||
}
|
||||
|
||||
.palette:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 8px 25px rgba(45, 90, 39, 0.15);
|
||||
}
|
||||
|
||||
.palette-header {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.palette-name {
|
||||
font-size: 1.4rem;
|
||||
color: #2d5a27;
|
||||
margin-bottom: 8px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.palette-description {
|
||||
color: #68d391;
|
||||
font-size: 0.95rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.colors-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.color-item {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
border: 1px solid rgba(104, 211, 145, 0.2);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.color-item:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(45, 90, 39, 0.1);
|
||||
}
|
||||
|
||||
.color-swatch {
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 10px;
|
||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.color-swatch::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: linear-gradient(45deg, transparent 45%, rgba(255,255,255,0.1) 50%, transparent 55%);
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.color-swatch:hover::after {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.color-name {
|
||||
font-weight: 600;
|
||||
color: #2d5a27;
|
||||
margin-bottom: 5px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.color-hex {
|
||||
font-family: 'Courier New', monospace;
|
||||
color: #4a5568;
|
||||
font-size: 0.85rem;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
.color-role {
|
||||
font-size: 0.8rem;
|
||||
color: #68d391;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* 底部 */
|
||||
.footer {
|
||||
text-align: center;
|
||||
padding: 30px 0;
|
||||
margin-top: 40px;
|
||||
color: #68d391;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
/* 平板端适配 */
|
||||
@media (max-width: 1024px) {
|
||||
.container {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 2.2rem;
|
||||
}
|
||||
|
||||
.colors-grid {
|
||||
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
/* 手机端适配 */
|
||||
@media (max-width: 768px) {
|
||||
.container {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.header {
|
||||
margin-bottom: 25px;
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.input-section {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.input-wrapper {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
#colorPicker {
|
||||
width: 100%;
|
||||
height: 44px;
|
||||
}
|
||||
|
||||
.colors-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.color-details {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.palette {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.palette-name {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.header h1 {
|
||||
font-size: 1.6rem;
|
||||
}
|
||||
|
||||
.input-section {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.palette {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.color-details {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
@@ -1,273 +0,0 @@
|
||||
{
|
||||
"code": 200,
|
||||
"message": "获取成功。数据来自官方/权威源头,以确保稳定与实时。开源地址 https://github.com/vikiboss/60s,反馈群 595941841",
|
||||
"data": {
|
||||
"input": {
|
||||
"hex": "#DE4F99",
|
||||
"rgb": {
|
||||
"r": 222,
|
||||
"g": 79,
|
||||
"b": 153
|
||||
},
|
||||
"hsl": {
|
||||
"h": 329,
|
||||
"s": 68,
|
||||
"l": 59
|
||||
},
|
||||
"name": "红色系"
|
||||
},
|
||||
"palettes": [
|
||||
{
|
||||
"name": "单色配色",
|
||||
"description": "基于同一色相,通过调整明度和饱和度创建的和谐配色方案,适合营造统一、专业的视觉效果",
|
||||
"colors": [
|
||||
{
|
||||
"hex": "#DE4F99",
|
||||
"name": "主色",
|
||||
"role": "primary",
|
||||
"theory": "基础色相"
|
||||
},
|
||||
{
|
||||
"hex": "#7C184C",
|
||||
"name": "深色变体",
|
||||
"role": "dark",
|
||||
"theory": "降低明度"
|
||||
},
|
||||
{
|
||||
"hex": "#EEA5CB",
|
||||
"name": "浅色变体",
|
||||
"role": "light",
|
||||
"theory": "提高明度"
|
||||
},
|
||||
{
|
||||
"hex": "#C96498",
|
||||
"name": "柔和变体",
|
||||
"role": "muted",
|
||||
"theory": "降低饱和度"
|
||||
},
|
||||
{
|
||||
"hex": "#ED4099",
|
||||
"name": "鲜艳变体",
|
||||
"role": "vibrant",
|
||||
"theory": "提高饱和度"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "互补配色",
|
||||
"description": "使用色轮上相对的颜色,创造强烈对比和视觉冲击力,适用于需要突出重点的设计",
|
||||
"colors": [
|
||||
{
|
||||
"hex": "#DE4F99",
|
||||
"name": "主色",
|
||||
"role": "primary",
|
||||
"theory": "基础色相"
|
||||
},
|
||||
{
|
||||
"hex": "#4FDE94",
|
||||
"name": "互补色",
|
||||
"role": "complementary",
|
||||
"theory": "色轮对面 +180°"
|
||||
},
|
||||
{
|
||||
"hex": "#F2BAD7",
|
||||
"name": "主色浅调",
|
||||
"role": "primary-light",
|
||||
"theory": "主色提高明度"
|
||||
},
|
||||
{
|
||||
"hex": "#BAF2D5",
|
||||
"name": "互补色浅调",
|
||||
"role": "complementary-light",
|
||||
"theory": "互补色提高明度"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "邻近配色",
|
||||
"description": "使用色轮上相邻的颜色,创造自然和谐的渐变效果,常见于自然景观中",
|
||||
"colors": [
|
||||
{
|
||||
"hex": "#DB4FDE",
|
||||
"name": "邻近色1",
|
||||
"role": "analogous-1",
|
||||
"theory": "色相 -30°"
|
||||
},
|
||||
{
|
||||
"hex": "#DE4F99",
|
||||
"name": "主色",
|
||||
"role": "primary",
|
||||
"theory": "基础色相"
|
||||
},
|
||||
{
|
||||
"hex": "#DE4F52",
|
||||
"name": "邻近色2",
|
||||
"role": "analogous-2",
|
||||
"theory": "色相 +30°"
|
||||
},
|
||||
{
|
||||
"hex": "#DE944F",
|
||||
"name": "邻近色3",
|
||||
"role": "analogous-3",
|
||||
"theory": "色相 +60°"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "三角配色",
|
||||
"description": "在色轮上形成等边三角形的三种颜色,提供丰富对比的同时保持和谐平衡",
|
||||
"colors": [
|
||||
{
|
||||
"hex": "#DE4F99",
|
||||
"name": "主色",
|
||||
"role": "primary",
|
||||
"theory": "基础色相"
|
||||
},
|
||||
{
|
||||
"hex": "#99DE4F",
|
||||
"name": "三角色1",
|
||||
"role": "triadic-1",
|
||||
"theory": "色相 +120°"
|
||||
},
|
||||
{
|
||||
"hex": "#4F99DE",
|
||||
"name": "三角色2",
|
||||
"role": "triadic-2",
|
||||
"theory": "色相 +240°"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "分裂互补配色",
|
||||
"description": "使用互补色两侧的颜色,比纯互补配色更柔和,同时保持强烈的视觉对比",
|
||||
"colors": [
|
||||
{
|
||||
"hex": "#DE4F99",
|
||||
"name": "主色",
|
||||
"role": "primary",
|
||||
"theory": "基础色相"
|
||||
},
|
||||
{
|
||||
"hex": "#52DE4F",
|
||||
"name": "分裂互补色1",
|
||||
"role": "split-comp-1",
|
||||
"theory": "互补色 -30°"
|
||||
},
|
||||
{
|
||||
"hex": "#4FDEDB",
|
||||
"name": "分裂互补色2",
|
||||
"role": "split-comp-2",
|
||||
"theory": "互补色 +30°"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "四边形配色",
|
||||
"description": "在色轮上形成正方形的四种颜色,提供最丰富的颜色变化,适合复杂的设计项目",
|
||||
"colors": [
|
||||
{
|
||||
"hex": "#DE4F99",
|
||||
"name": "主色",
|
||||
"role": "primary",
|
||||
"theory": "基础色相"
|
||||
},
|
||||
{
|
||||
"hex": "#DEDB4F",
|
||||
"name": "四边形色1",
|
||||
"role": "square-1",
|
||||
"theory": "色相 +90°"
|
||||
},
|
||||
{
|
||||
"hex": "#4FDE94",
|
||||
"name": "四边形色2",
|
||||
"role": "square-2",
|
||||
"theory": "色相 +180°"
|
||||
},
|
||||
{
|
||||
"hex": "#4F52DE",
|
||||
"name": "四边形色3",
|
||||
"role": "square-3",
|
||||
"theory": "色相 +270°"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Web 设计配色",
|
||||
"description": "专为 Web 界面设计优化的配色方案,考虑了可访问性和用户体验",
|
||||
"colors": [
|
||||
{
|
||||
"hex": "#DE4F99",
|
||||
"name": "品牌主色",
|
||||
"role": "brand-primary",
|
||||
"theory": "品牌识别色"
|
||||
},
|
||||
{
|
||||
"hex": "#982F65",
|
||||
"name": "按钮悬停",
|
||||
"role": "hover-state",
|
||||
"theory": "主色加深变体"
|
||||
},
|
||||
{
|
||||
"hex": "#F6E9F0",
|
||||
"name": "背景浅色",
|
||||
"role": "background",
|
||||
"theory": "高明度低饱和度"
|
||||
},
|
||||
{
|
||||
"hex": "#1BDE7A",
|
||||
"name": "强调色",
|
||||
"role": "accent",
|
||||
"theory": "互补色系强调"
|
||||
},
|
||||
{
|
||||
"hex": "#6B7280",
|
||||
"name": "文本辅助",
|
||||
"role": "text-secondary",
|
||||
"theory": "中性灰色文本"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "暖色调配色",
|
||||
"description": "基于暖色系的配色方案,营造温暖、活力和友好的氛围,适合餐饮、儿童产品等",
|
||||
"colors": [
|
||||
{
|
||||
"hex": "#DE4F99",
|
||||
"name": "主暖色",
|
||||
"role": "warm-primary",
|
||||
"theory": "暖色系基调"
|
||||
},
|
||||
{
|
||||
"hex": "#DE4FC8",
|
||||
"name": "暖色变体1",
|
||||
"role": "warm-variant-1",
|
||||
"theory": "暖色范围内调整"
|
||||
},
|
||||
{
|
||||
"hex": "#DE4F5E",
|
||||
"name": "暖色变体2",
|
||||
"role": "warm-variant-2",
|
||||
"theory": "暖色范围内调整"
|
||||
},
|
||||
{
|
||||
"hex": "#EEA5CB",
|
||||
"name": "暖色浅调",
|
||||
"role": "warm-tint",
|
||||
"theory": "提高明度的暖色"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"color_theory": "基于色彩理论生成的专业配色方案",
|
||||
"total_palettes": 8,
|
||||
"applications": [
|
||||
"Web 设计",
|
||||
"UI/UX",
|
||||
"品牌设计",
|
||||
"室内设计",
|
||||
"服装搭配"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,232 +0,0 @@
|
||||
/* 高维度背景特效样式 - 神秘高级风格 */
|
||||
|
||||
/* 背景容器 */
|
||||
.background-container {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 1;
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
background: radial-gradient(ellipse at center,
|
||||
rgba(15, 0, 30, 0.95) 0%,
|
||||
rgba(5, 0, 15, 0.98) 50%,
|
||||
rgba(0, 0, 0, 1) 100%);
|
||||
}
|
||||
|
||||
/* 几何网格层 */
|
||||
.geometric-grid {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image:
|
||||
linear-gradient(rgba(138, 43, 226, 0.1) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(138, 43, 226, 0.1) 1px, transparent 1px),
|
||||
linear-gradient(rgba(75, 0, 130, 0.05) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(75, 0, 130, 0.05) 1px, transparent 1px);
|
||||
background-size: 100px 100px, 100px 100px, 20px 20px, 20px 20px;
|
||||
animation: gridPulse 8s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes gridPulse {
|
||||
0%, 100% { opacity: 0.3; transform: scale(1); }
|
||||
50% { opacity: 0.6; transform: scale(1.02); }
|
||||
}
|
||||
|
||||
/* 神经网络效果 */
|
||||
.neural-network {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background:
|
||||
radial-gradient(circle at 20% 30%, rgba(138, 43, 226, 0.15) 2px, transparent 2px),
|
||||
radial-gradient(circle at 80% 20%, rgba(75, 0, 130, 0.12) 1px, transparent 1px),
|
||||
radial-gradient(circle at 40% 70%, rgba(147, 0, 211, 0.1) 1.5px, transparent 1.5px),
|
||||
radial-gradient(circle at 90% 80%, rgba(138, 43, 226, 0.08) 1px, transparent 1px),
|
||||
radial-gradient(circle at 10% 90%, rgba(75, 0, 130, 0.1) 2px, transparent 2px);
|
||||
background-size: 200px 200px, 150px 150px, 300px 300px, 180px 180px, 250px 250px;
|
||||
animation: neuralFlow 15s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes neuralFlow {
|
||||
0% { transform: translate(0, 0) rotate(0deg); }
|
||||
25% { transform: translate(-10px, -5px) rotate(90deg); }
|
||||
50% { transform: translate(-5px, -10px) rotate(180deg); }
|
||||
75% { transform: translate(5px, -5px) rotate(270deg); }
|
||||
100% { transform: translate(0, 0) rotate(360deg); }
|
||||
}
|
||||
|
||||
/* 粒子系统 */
|
||||
.particle-system {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image:
|
||||
radial-gradient(circle, rgba(138, 43, 226, 0.4) 1px, transparent 1px),
|
||||
radial-gradient(circle, rgba(75, 0, 130, 0.3) 0.5px, transparent 0.5px),
|
||||
radial-gradient(circle, rgba(147, 0, 211, 0.2) 0.8px, transparent 0.8px);
|
||||
background-size: 80px 80px, 120px 120px, 160px 160px;
|
||||
background-position: 0 0, 40px 40px, 80px 80px;
|
||||
animation: particleFloat 20s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes particleFloat {
|
||||
0%, 100% { transform: translateY(0px) translateX(0px); }
|
||||
25% { transform: translateY(-20px) translateX(10px); }
|
||||
50% { transform: translateY(-10px) translateX(-15px); }
|
||||
75% { transform: translateY(-30px) translateX(5px); }
|
||||
}
|
||||
|
||||
/* 扫描线效果 */
|
||||
.scan-lines {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: repeating-linear-gradient(
|
||||
0deg,
|
||||
transparent 0px,
|
||||
transparent 2px,
|
||||
rgba(138, 43, 226, 0.03) 2px,
|
||||
rgba(138, 43, 226, 0.03) 4px
|
||||
);
|
||||
animation: scanMove 3s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes scanMove {
|
||||
0% { transform: translateY(-100%); }
|
||||
100% { transform: translateY(100%); }
|
||||
}
|
||||
|
||||
/* 全息投影效果 */
|
||||
.holographic-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background:
|
||||
linear-gradient(45deg,
|
||||
transparent 30%,
|
||||
rgba(138, 43, 226, 0.05) 50%,
|
||||
transparent 70%),
|
||||
linear-gradient(-45deg,
|
||||
transparent 30%,
|
||||
rgba(75, 0, 130, 0.03) 50%,
|
||||
transparent 70%);
|
||||
background-size: 200px 200px, 150px 150px;
|
||||
animation: holographicShift 12s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes holographicShift {
|
||||
0%, 100% {
|
||||
background-position: 0% 0%, 100% 100%;
|
||||
opacity: 0.7;
|
||||
}
|
||||
50% {
|
||||
background-position: 100% 100%, 0% 0%;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* 数据流效果 */
|
||||
.data-stream {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background:
|
||||
linear-gradient(90deg,
|
||||
transparent 0%,
|
||||
rgba(138, 43, 226, 0.1) 50%,
|
||||
transparent 100%);
|
||||
background-size: 300px 100%;
|
||||
animation: dataFlow 8s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes dataFlow {
|
||||
0% { transform: translateX(-100%); }
|
||||
100% { transform: translateX(100%); }
|
||||
}
|
||||
|
||||
/* 量子波动效果 */
|
||||
.quantum-waves {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background:
|
||||
radial-gradient(ellipse 200px 100px at 50% 0%,
|
||||
rgba(138, 43, 226, 0.1) 0%,
|
||||
transparent 50%),
|
||||
radial-gradient(ellipse 300px 150px at 50% 100%,
|
||||
rgba(75, 0, 130, 0.08) 0%,
|
||||
transparent 50%);
|
||||
animation: quantumPulse 10s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes quantumPulse {
|
||||
0%, 100% {
|
||||
transform: scale(1) rotate(0deg);
|
||||
opacity: 0.5;
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.1) rotate(180deg);
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
/* 响应式优化 */
|
||||
@media (max-width: 768px) {
|
||||
.geometric-grid {
|
||||
background-size: 50px 50px, 50px 50px, 10px 10px, 10px 10px;
|
||||
}
|
||||
|
||||
.neural-network {
|
||||
background-size: 100px 100px, 75px 75px, 150px 150px, 90px 90px, 125px 125px;
|
||||
}
|
||||
|
||||
.particle-system {
|
||||
background-size: 40px 40px, 60px 60px, 80px 80px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 减少动画偏好 */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.geometric-grid,
|
||||
.neural-network,
|
||||
.particle-system,
|
||||
.scan-lines,
|
||||
.holographic-overlay,
|
||||
.data-stream,
|
||||
.quantum-waves {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* 高对比度模式 */
|
||||
@media (prefers-contrast: high) {
|
||||
.background-container {
|
||||
background: radial-gradient(ellipse at center,
|
||||
rgba(25, 0, 50, 0.95) 0%,
|
||||
rgba(10, 0, 25, 0.98) 50%,
|
||||
rgba(0, 0, 0, 1) 100%);
|
||||
}
|
||||
|
||||
.geometric-grid {
|
||||
background-image:
|
||||
linear-gradient(rgba(200, 100, 255, 0.2) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(200, 100, 255, 0.2) 1px, transparent 1px);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,227 +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>链接OG信息查询 - 神秘解析器</title>
|
||||
<meta name="description" content="高级链接OG信息查询工具,解析网页元数据">
|
||||
<link rel="stylesheet" href="css/style.css">
|
||||
<link rel="stylesheet" href="css/background.css">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
||||
</head>
|
||||
<body>
|
||||
<!-- 背景特效容器 -->
|
||||
<div class="background-container">
|
||||
<div class="matrix-rain"></div>
|
||||
<div class="geometric-shapes"></div>
|
||||
<div class="neural-network"></div>
|
||||
</div>
|
||||
|
||||
<!-- 主容器 -->
|
||||
<div class="main-container">
|
||||
<!-- 头部区域 -->
|
||||
<header class="header">
|
||||
<div class="header-content">
|
||||
<div class="logo-section">
|
||||
<i class="fas fa-link logo-icon"></i>
|
||||
<h1 class="title">OG 解析器</h1>
|
||||
<span class="subtitle">链接元数据神秘解析</span>
|
||||
</div>
|
||||
<div class="status-indicator">
|
||||
<div class="pulse-dot"></div>
|
||||
<span class="status-text">系统就绪</span>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- 查询区域 -->
|
||||
<section class="query-section">
|
||||
<div class="input-container">
|
||||
<div class="input-wrapper">
|
||||
<i class="fas fa-globe input-icon"></i>
|
||||
<input type="url" id="url-input" placeholder="输入链接地址进行深度解析..." class="url-input">
|
||||
<div class="input-border"></div>
|
||||
</div>
|
||||
<button id="analyze-btn" class="analyze-btn">
|
||||
<span class="btn-text">开始解析</span>
|
||||
<div class="btn-effects">
|
||||
<div class="ripple"></div>
|
||||
<div class="glow"></div>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<div id="loading" class="loading-container" style="display: none;">
|
||||
<div class="loading-content">
|
||||
<div class="scanner">
|
||||
<div class="scanner-line"></div>
|
||||
<div class="scanner-grid">
|
||||
<div class="grid-line"></div>
|
||||
<div class="grid-line"></div>
|
||||
<div class="grid-line"></div>
|
||||
<div class="grid-line"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="loading-text">
|
||||
<span class="loading-title">正在解析链接</span>
|
||||
<span class="loading-subtitle">深度扫描元数据中...</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 结果展示区域 -->
|
||||
<section id="results" class="results-section" style="display: none;">
|
||||
<div class="results-header">
|
||||
<h2 class="results-title">
|
||||
<i class="fas fa-chart-network"></i>
|
||||
解析结果
|
||||
</h2>
|
||||
<div class="results-actions">
|
||||
<button id="copy-btn" class="action-btn">
|
||||
<i class="fas fa-copy"></i>
|
||||
<span>复制数据</span>
|
||||
</button>
|
||||
<button id="clear-btn" class="action-btn">
|
||||
<i class="fas fa-trash"></i>
|
||||
<span>清除结果</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="og-card">
|
||||
<!-- 基础信息 -->
|
||||
<div class="info-section basic-info">
|
||||
<div class="section-header">
|
||||
<i class="fas fa-info-circle"></i>
|
||||
<span>基础信息</span>
|
||||
</div>
|
||||
<div class="info-grid">
|
||||
<div class="info-item">
|
||||
<label>标题</label>
|
||||
<div id="og-title" class="info-value">-</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<label>描述</label>
|
||||
<div id="og-description" class="info-value">-</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<label>网站名称</label>
|
||||
<div id="og-site-name" class="info-value">-</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<label>类型</label>
|
||||
<div id="og-type" class="info-value">-</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 媒体信息 -->
|
||||
<div class="info-section media-info">
|
||||
<div class="section-header">
|
||||
<i class="fas fa-image"></i>
|
||||
<span>媒体信息</span>
|
||||
</div>
|
||||
<div class="media-preview" id="media-preview">
|
||||
<div class="no-media">
|
||||
<i class="fas fa-image-slash"></i>
|
||||
<span>暂无媒体内容</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="media-details">
|
||||
<div class="info-item">
|
||||
<label>图片URL</label>
|
||||
<div id="og-image" class="info-value url-value">-</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<label>图片描述</label>
|
||||
<div id="og-image-alt" class="info-value">-</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 技术信息 -->
|
||||
<div class="info-section tech-info">
|
||||
<div class="section-header">
|
||||
<i class="fas fa-code"></i>
|
||||
<span>技术信息</span>
|
||||
</div>
|
||||
<div class="info-grid">
|
||||
<div class="info-item">
|
||||
<label>URL</label>
|
||||
<div id="og-url" class="info-value url-value">-</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<label>域名</label>
|
||||
<div id="og-domain" class="info-value">-</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<label>语言</label>
|
||||
<div id="og-locale" class="info-value">-</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<label>更新时间</label>
|
||||
<div id="og-updated-time" class="info-value">-</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<label>响应时间</label>
|
||||
<div id="response-time" class="info-value">-</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 错误信息 -->
|
||||
<div id="error" class="error-container" style="display: none;">
|
||||
<div class="error-content">
|
||||
<div class="error-icon">
|
||||
<i class="fas fa-exclamation-triangle"></i>
|
||||
</div>
|
||||
<div class="error-text">
|
||||
<h3 class="error-title">解析失败</h3>
|
||||
<p id="error-message" class="error-message">未知错误</p>
|
||||
</div>
|
||||
<button id="retryBtn" class="retry-btn">
|
||||
<i class="fas fa-redo"></i>
|
||||
<span>重新尝试</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 提示消息 -->
|
||||
<div id="tip-message" class="tip-container">
|
||||
<div class="tip-content">
|
||||
<i class="fas fa-lightbulb tip-icon"></i>
|
||||
<span class="tip-text"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Toast消息 -->
|
||||
<div id="toast" class="toast-container">
|
||||
<div class="toast-content">
|
||||
<i class="toast-icon"></i>
|
||||
<span class="toast-message"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 页脚 -->
|
||||
<footer class="footer">
|
||||
<div class="footer-content">
|
||||
<p class="footer-text">
|
||||
<i class="fas fa-shield-alt"></i>
|
||||
高级链接解析系统 | 神秘数据挖掘
|
||||
</p>
|
||||
<div class="footer-links">
|
||||
<span class="footer-link">隐私保护</span>
|
||||
<span class="footer-divider">|</span>
|
||||
<span class="footer-link">安全解析</span>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="js/script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,617 +0,0 @@
|
||||
// 链接OG信息查询 - JavaScript功能代码
|
||||
// 神秘高级风格的交互体验
|
||||
|
||||
class OGAnalyzer {
|
||||
constructor() {
|
||||
this.apiUrl = 'https://60s.api.shumengya.top/v2/og';
|
||||
this.isAnalyzing = false;
|
||||
this.currentUrl = '';
|
||||
this.animationFrameId = null;
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
this.bindEvents();
|
||||
this.createBackgroundEffects();
|
||||
this.initializeAnimations();
|
||||
this.showWelcomeMessage();
|
||||
this.initPageAnimations();
|
||||
}
|
||||
|
||||
// 初始化页面动画
|
||||
initPageAnimations() {
|
||||
// 延迟添加动画类,确保CSS已加载
|
||||
setTimeout(() => {
|
||||
const header = document.querySelector('.header');
|
||||
const querySection = document.querySelector('.query-section');
|
||||
|
||||
if (header) header.classList.add('animate-in');
|
||||
if (querySection) querySection.classList.add('animate-in');
|
||||
}, 100);
|
||||
}
|
||||
|
||||
bindEvents() {
|
||||
const urlInput = document.getElementById('url-input');
|
||||
const analyzeBtn = document.getElementById('analyze-btn');
|
||||
const copyBtn = document.getElementById('copy-btn');
|
||||
const clearBtn = document.getElementById('clear-btn');
|
||||
|
||||
// 输入框事件
|
||||
urlInput.addEventListener('input', (e) => this.handleUrlInput(e));
|
||||
urlInput.addEventListener('keypress', (e) => {
|
||||
if (e.key === 'Enter' && !this.isAnalyzing) {
|
||||
this.analyzeUrl();
|
||||
}
|
||||
});
|
||||
urlInput.addEventListener('focus', () => this.handleInputFocus());
|
||||
urlInput.addEventListener('blur', () => this.handleInputBlur());
|
||||
|
||||
// 按钮事件
|
||||
analyzeBtn.addEventListener('click', () => this.analyzeUrl());
|
||||
copyBtn.addEventListener('click', () => this.copyResults());
|
||||
clearBtn.addEventListener('click', () => this.clearResults());
|
||||
|
||||
// 键盘快捷键
|
||||
document.addEventListener('keydown', (e) => this.handleKeyboard(e));
|
||||
}
|
||||
|
||||
handleUrlInput(e) {
|
||||
const url = e.target.value.trim();
|
||||
const analyzeBtn = document.getElementById('analyze-btn');
|
||||
|
||||
if (this.isValidUrl(url)) {
|
||||
analyzeBtn.classList.add('ready');
|
||||
e.target.classList.remove('error');
|
||||
} else {
|
||||
analyzeBtn.classList.remove('ready');
|
||||
if (url.length > 0) {
|
||||
e.target.classList.add('error');
|
||||
} else {
|
||||
e.target.classList.remove('error');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleInputFocus() {
|
||||
const inputContainer = document.querySelector('.input-container');
|
||||
inputContainer.classList.add('focused');
|
||||
this.createInputGlow();
|
||||
}
|
||||
|
||||
handleInputBlur() {
|
||||
const inputContainer = document.querySelector('.input-container');
|
||||
inputContainer.classList.remove('focused');
|
||||
}
|
||||
|
||||
handleKeyboard(e) {
|
||||
// Ctrl/Cmd + Enter 快速分析
|
||||
if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
if (!this.isAnalyzing) {
|
||||
this.analyzeUrl();
|
||||
}
|
||||
}
|
||||
|
||||
// Escape 清除结果
|
||||
if (e.key === 'Escape') {
|
||||
this.clearResults();
|
||||
}
|
||||
}
|
||||
|
||||
isValidUrl(string) {
|
||||
try {
|
||||
const url = new URL(string);
|
||||
return url.protocol === 'http:' || url.protocol === 'https:';
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async analyzeUrl() {
|
||||
const urlInput = document.getElementById('url-input');
|
||||
const url = urlInput.value.trim();
|
||||
|
||||
if (!this.isValidUrl(url)) {
|
||||
this.showError('请输入有效的URL地址');
|
||||
this.shakeInput();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.isAnalyzing) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.currentUrl = url;
|
||||
this.isAnalyzing = true;
|
||||
this.startTime = Date.now(); // 记录开始时间
|
||||
this.showLoading();
|
||||
this.hideError();
|
||||
this.hideResults();
|
||||
|
||||
try {
|
||||
const response = await fetch(`${this.apiUrl}?url=${encodeURIComponent(url)}`);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.code === 200 && data.data) {
|
||||
await this.displayResults(data.data);
|
||||
this.showSuccessMessage('分析完成!');
|
||||
|
||||
// 添加按钮闪烁效果
|
||||
const analyzeBtn = document.getElementById('analyze-btn');
|
||||
analyzeBtn.classList.add('flash');
|
||||
setTimeout(() => {
|
||||
analyzeBtn.classList.remove('flash');
|
||||
}, 300);
|
||||
} else {
|
||||
throw new Error(data.message || '获取OG信息失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('分析失败:', error);
|
||||
this.showError(`分析失败: ${error.message}`);
|
||||
} finally {
|
||||
this.isAnalyzing = false;
|
||||
this.hideLoading();
|
||||
}
|
||||
}
|
||||
|
||||
showLoading() {
|
||||
const loadingElement = document.getElementById('loading');
|
||||
const analyzeBtn = document.getElementById('analyze-btn');
|
||||
|
||||
loadingElement.classList.add('active');
|
||||
analyzeBtn.disabled = true;
|
||||
analyzeBtn.textContent = '分析中...';
|
||||
|
||||
this.startScannerAnimation();
|
||||
}
|
||||
|
||||
hideLoading() {
|
||||
const loadingElement = document.getElementById('loading');
|
||||
const analyzeBtn = document.getElementById('analyze-btn');
|
||||
|
||||
loadingElement.classList.remove('active');
|
||||
analyzeBtn.disabled = false;
|
||||
analyzeBtn.textContent = '开始分析';
|
||||
|
||||
this.stopScannerAnimation();
|
||||
}
|
||||
|
||||
async displayResults(data) {
|
||||
const resultsElement = document.getElementById('results');
|
||||
const ogCard = document.getElementById('og-card');
|
||||
|
||||
// 检查是否有有效数据 - 放宽检查条件,只要有任何非空字段就显示
|
||||
const hasValidData = Object.values(data).some(value => {
|
||||
if (value === null || value === undefined) return false;
|
||||
if (typeof value === 'string') return value.trim() !== '';
|
||||
return true; // 其他类型的值都认为是有效的
|
||||
});
|
||||
|
||||
if (!hasValidData) {
|
||||
this.showError('该链接暂无可获取的OG信息,请检查链接是否正确或稍后重试');
|
||||
return;
|
||||
}
|
||||
|
||||
// 基础信息 - 只显示有数据的字段
|
||||
this.updateElementWithVisibility('og-title', data.title, '标题');
|
||||
this.updateElementWithVisibility('og-description', data.description, '描述');
|
||||
this.updateElement('og-url', data.url || this.currentUrl); // URL始终显示
|
||||
this.updateElementWithVisibility('og-site-name', data.site_name, '网站名称');
|
||||
this.updateElement('og-type', data.type || 'website'); // 类型始终显示
|
||||
|
||||
// 媒体信息
|
||||
this.updateImageElementWithVisibility('og-image', data.image);
|
||||
this.updateElementWithVisibility('og-image-alt', data.image_alt, '图片描述');
|
||||
|
||||
// 技术信息
|
||||
this.updateElementWithVisibility('og-locale', data.locale, '语言');
|
||||
this.updateElementWithVisibility('og-updated-time', this.formatDate(data.updated_time), '更新时间');
|
||||
this.updateElement('response-time', `${Date.now() - this.startTime}ms`); // 响应时间始终显示
|
||||
|
||||
// 显示结果
|
||||
resultsElement.style.display = 'block';
|
||||
resultsElement.classList.add('active');
|
||||
|
||||
// 添加动画效果
|
||||
await this.animateResults();
|
||||
|
||||
// 启用操作按钮
|
||||
document.getElementById('copy-btn').disabled = false;
|
||||
document.getElementById('clear-btn').disabled = false;
|
||||
}
|
||||
|
||||
updateElement(id, content) {
|
||||
const element = document.getElementById(id);
|
||||
if (element) {
|
||||
element.textContent = content;
|
||||
}
|
||||
}
|
||||
|
||||
updateElementWithVisibility(id, content, fieldName) {
|
||||
const element = document.getElementById(id);
|
||||
if (!element) return;
|
||||
|
||||
const parentItem = element.closest('.info-item');
|
||||
if (!parentItem) return;
|
||||
|
||||
if (content && content.trim() !== '') {
|
||||
element.textContent = content;
|
||||
parentItem.style.display = 'block';
|
||||
} else {
|
||||
parentItem.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
updateImageElement(id, imageSrc) {
|
||||
const element = document.getElementById(id);
|
||||
if (element && imageSrc) {
|
||||
element.src = imageSrc;
|
||||
element.style.display = 'block';
|
||||
element.onerror = () => {
|
||||
element.style.display = 'none';
|
||||
const placeholder = element.nextElementSibling;
|
||||
if (placeholder && placeholder.classList.contains('image-placeholder')) {
|
||||
placeholder.style.display = 'flex';
|
||||
}
|
||||
};
|
||||
} else if (element) {
|
||||
element.style.display = 'none';
|
||||
const placeholder = element.nextElementSibling;
|
||||
if (placeholder && placeholder.classList.contains('image-placeholder')) {
|
||||
placeholder.style.display = 'flex';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateImageElementWithVisibility(id, imageSrc) {
|
||||
const element = document.getElementById(id);
|
||||
const mediaSection = document.querySelector('.media-info');
|
||||
const mediaPreview = document.getElementById('media-preview');
|
||||
|
||||
if (imageSrc && imageSrc.trim() !== '') {
|
||||
element.textContent = imageSrc;
|
||||
if (mediaSection) mediaSection.style.display = 'block';
|
||||
|
||||
if (mediaPreview) {
|
||||
mediaPreview.innerHTML = `
|
||||
<img src="${imageSrc}" alt="OG Image" class="og-preview-image"
|
||||
onerror="this.style.display='none'; this.nextElementSibling.style.display='block';">
|
||||
<div class="no-media" style="display: none;">
|
||||
<i class="fas fa-image-slash"></i>
|
||||
<span>图片加载失败</span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
} else {
|
||||
if (mediaSection) mediaSection.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
formatDate(timestamp) {
|
||||
if (!timestamp) return '未知';
|
||||
try {
|
||||
const date = new Date(timestamp);
|
||||
return date.toLocaleString('zh-CN');
|
||||
} catch (e) {
|
||||
return '格式错误';
|
||||
}
|
||||
}
|
||||
|
||||
async animateResults() {
|
||||
const cards = document.querySelectorAll('.info-card');
|
||||
|
||||
for (let i = 0; i < cards.length; i++) {
|
||||
setTimeout(() => {
|
||||
cards[i].classList.add('animate-in');
|
||||
}, i * 100);
|
||||
}
|
||||
|
||||
// 等待动画完成
|
||||
await new Promise(resolve => setTimeout(resolve, cards.length * 100 + 300));
|
||||
}
|
||||
|
||||
showError(message) {
|
||||
const errorElement = document.getElementById('error-message');
|
||||
const errorText = errorElement.querySelector('.error-text');
|
||||
const inputContainer = document.querySelector('.input-container');
|
||||
|
||||
errorText.textContent = message;
|
||||
errorElement.classList.add('active');
|
||||
|
||||
// 添加震动效果
|
||||
if (inputContainer) {
|
||||
inputContainer.classList.add('shake');
|
||||
setTimeout(() => {
|
||||
inputContainer.classList.remove('shake');
|
||||
}, 600);
|
||||
}
|
||||
|
||||
// 自动隐藏错误信息
|
||||
setTimeout(() => {
|
||||
this.hideError();
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
hideError() {
|
||||
const errorElement = document.getElementById('error-message');
|
||||
errorElement.classList.remove('active');
|
||||
}
|
||||
|
||||
hideResults() {
|
||||
const resultsElement = document.getElementById('results');
|
||||
resultsElement.style.display = 'none';
|
||||
resultsElement.classList.remove('active');
|
||||
|
||||
// 重置动画状态
|
||||
const cards = document.querySelectorAll('.info-card');
|
||||
cards.forEach(card => card.classList.remove('animate-in'));
|
||||
}
|
||||
|
||||
showSuccessMessage(message) {
|
||||
const tipElement = document.getElementById('tip-message');
|
||||
const tipText = tipElement.querySelector('.tip-text');
|
||||
|
||||
tipText.textContent = message;
|
||||
tipElement.classList.add('active');
|
||||
|
||||
setTimeout(() => {
|
||||
tipElement.classList.remove('active');
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
shakeInput() {
|
||||
const inputContainer = document.querySelector('.input-container');
|
||||
inputContainer.classList.add('shake');
|
||||
|
||||
setTimeout(() => {
|
||||
inputContainer.classList.remove('shake');
|
||||
}, 600);
|
||||
}
|
||||
|
||||
copyResults() {
|
||||
const ogData = {
|
||||
title: document.getElementById('og-title').textContent,
|
||||
description: document.getElementById('og-description').textContent,
|
||||
url: document.getElementById('og-url').textContent,
|
||||
site_name: document.getElementById('og-site-name').textContent,
|
||||
type: document.getElementById('og-type').textContent,
|
||||
image: document.getElementById('og-image').src,
|
||||
locale: document.getElementById('og-locale').textContent
|
||||
};
|
||||
|
||||
const jsonString = JSON.stringify(ogData, null, 2);
|
||||
|
||||
navigator.clipboard.writeText(jsonString).then(() => {
|
||||
this.showSuccessMessage('结果已复制到剪贴板!');
|
||||
this.flashCopyButton();
|
||||
}).catch(err => {
|
||||
console.error('复制失败:', err);
|
||||
this.showError('复制失败,请手动选择内容');
|
||||
});
|
||||
}
|
||||
|
||||
flashCopyButton() {
|
||||
const copyBtn = document.getElementById('copy-btn');
|
||||
copyBtn.classList.add('flash');
|
||||
|
||||
setTimeout(() => {
|
||||
copyBtn.classList.remove('flash');
|
||||
}, 300);
|
||||
}
|
||||
|
||||
clearResults() {
|
||||
const urlInput = document.getElementById('url-input');
|
||||
const resultsElement = document.getElementById('results');
|
||||
const errorElement = document.getElementById('error-message');
|
||||
|
||||
urlInput.value = '';
|
||||
urlInput.classList.remove('error');
|
||||
resultsElement.style.display = 'none';
|
||||
resultsElement.classList.remove('active');
|
||||
errorElement.classList.remove('active');
|
||||
|
||||
document.getElementById('analyze-btn').classList.remove('ready');
|
||||
document.getElementById('copy-btn').disabled = true;
|
||||
document.getElementById('clear-btn').disabled = true;
|
||||
|
||||
this.currentUrl = '';
|
||||
|
||||
// 重置所有字段的显示状态
|
||||
const infoItems = document.querySelectorAll('.info-item');
|
||||
infoItems.forEach(item => item.style.display = 'block');
|
||||
|
||||
const mediaSection = document.querySelector('.media-info');
|
||||
if (mediaSection) mediaSection.style.display = 'block';
|
||||
|
||||
// 重置动画状态
|
||||
const cards = document.querySelectorAll('.info-card');
|
||||
cards.forEach(card => card.classList.remove('animate-in'));
|
||||
|
||||
this.showSuccessMessage('已清除所有内容');
|
||||
}
|
||||
|
||||
createBackgroundEffects() {
|
||||
const container = document.querySelector('.background-container');
|
||||
|
||||
// 创建各种背景效果层
|
||||
const effects = [
|
||||
'geometric-grid',
|
||||
'neural-network',
|
||||
'particle-system',
|
||||
'scan-lines',
|
||||
'holographic-overlay',
|
||||
'data-stream',
|
||||
'quantum-waves'
|
||||
];
|
||||
|
||||
effects.forEach(effectClass => {
|
||||
const layer = document.createElement('div');
|
||||
layer.className = effectClass;
|
||||
container.appendChild(layer);
|
||||
});
|
||||
}
|
||||
|
||||
createInputGlow() {
|
||||
const inputContainer = document.querySelector('.input-container');
|
||||
|
||||
// 创建光晕效果
|
||||
const glow = document.createElement('div');
|
||||
glow.className = 'input-glow';
|
||||
inputContainer.appendChild(glow);
|
||||
|
||||
setTimeout(() => {
|
||||
if (glow.parentNode) {
|
||||
glow.remove();
|
||||
}
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
startScannerAnimation() {
|
||||
const scanner = document.querySelector('.scanner');
|
||||
if (scanner) {
|
||||
scanner.classList.add('active');
|
||||
}
|
||||
}
|
||||
|
||||
stopScannerAnimation() {
|
||||
const scanner = document.querySelector('.scanner');
|
||||
if (scanner) {
|
||||
scanner.classList.remove('active');
|
||||
}
|
||||
}
|
||||
|
||||
initializeAnimations() {
|
||||
// 初始化页面动画
|
||||
const header = document.querySelector('.header');
|
||||
const querySection = document.querySelector('.query-section');
|
||||
|
||||
setTimeout(() => {
|
||||
header.classList.add('animate-in');
|
||||
}, 100);
|
||||
|
||||
setTimeout(() => {
|
||||
querySection.classList.add('animate-in');
|
||||
}, 300);
|
||||
}
|
||||
|
||||
showWelcomeMessage() {
|
||||
const tips = [
|
||||
'支持分析网页的标题、描述、图片等元信息',
|
||||
'可以预览社交媒体分享时的显示效果',
|
||||
'检测网页的SEO优化情况',
|
||||
'分析Open Graph协议标签'
|
||||
];
|
||||
|
||||
setTimeout(() => {
|
||||
this.showSuccessMessage('欢迎使用链接OG信息分析器!');
|
||||
}, 1000);
|
||||
|
||||
// 显示提示信息
|
||||
this.showTips(tips);
|
||||
}
|
||||
|
||||
// 显示提示信息
|
||||
showTips(tips) {
|
||||
const tipElement = document.getElementById('tip-message');
|
||||
const tipText = tipElement.querySelector('.tip-text');
|
||||
|
||||
let currentTip = 0;
|
||||
|
||||
const showNextTip = () => {
|
||||
tipText.textContent = tips[currentTip];
|
||||
tipElement.classList.add('active');
|
||||
tipElement.style.animation = 'fadeInUp 0.5s ease-out';
|
||||
|
||||
setTimeout(() => {
|
||||
tipElement.style.animation = 'fadeOutDown 0.5s ease-in';
|
||||
setTimeout(() => {
|
||||
tipElement.classList.remove('active');
|
||||
currentTip = (currentTip + 1) % tips.length;
|
||||
}, 500);
|
||||
}, 3000);
|
||||
};
|
||||
|
||||
// 首次显示
|
||||
setTimeout(showNextTip, 2000);
|
||||
|
||||
// 每8秒显示一次
|
||||
setInterval(showNextTip, 8000);
|
||||
}
|
||||
}
|
||||
|
||||
// 工具函数
|
||||
function debounce(func, wait) {
|
||||
let timeout;
|
||||
return function executedFunction(...args) {
|
||||
const later = () => {
|
||||
clearTimeout(timeout);
|
||||
func(...args);
|
||||
};
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(later, wait);
|
||||
};
|
||||
}
|
||||
|
||||
function throttle(func, limit) {
|
||||
let inThrottle;
|
||||
return function() {
|
||||
const args = arguments;
|
||||
const context = this;
|
||||
if (!inThrottle) {
|
||||
func.apply(context, args);
|
||||
inThrottle = true;
|
||||
setTimeout(() => inThrottle = false, limit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 页面加载完成后初始化
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// 检查必要的DOM元素
|
||||
const requiredElements = [
|
||||
'url-input', 'analyze-btn', 'copy-btn', 'clear-btn',
|
||||
'loading', 'results', 'error-message', 'tip-message'
|
||||
];
|
||||
|
||||
const missingElements = requiredElements.filter(id => !document.getElementById(id));
|
||||
|
||||
if (missingElements.length > 0) {
|
||||
console.error('缺少必要的DOM元素:', missingElements);
|
||||
return;
|
||||
}
|
||||
|
||||
// 初始化应用
|
||||
window.ogAnalyzer = new OGAnalyzer();
|
||||
|
||||
// 添加全局错误处理
|
||||
window.addEventListener('error', (e) => {
|
||||
console.error('全局错误:', e.error);
|
||||
if (window.ogAnalyzer) {
|
||||
window.ogAnalyzer.showError('发生未知错误,请刷新页面重试');
|
||||
}
|
||||
});
|
||||
|
||||
// 添加网络状态监听
|
||||
window.addEventListener('online', () => {
|
||||
if (window.ogAnalyzer) {
|
||||
window.ogAnalyzer.showSuccessMessage('网络连接已恢复');
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener('offline', () => {
|
||||
if (window.ogAnalyzer) {
|
||||
window.ogAnalyzer.showError('网络连接已断开');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 导出给其他模块使用
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = { OGAnalyzer, debounce, throttle };
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
{
|
||||
"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
|
||||
}
|
||||
@@ -1,252 +0,0 @@
|
||||
/* 背景样式文件 */
|
||||
|
||||
/* 主背景渐变 */
|
||||
body {
|
||||
background: linear-gradient(135deg, #e8f5e8 0%, #f0f9f0 25%, #f8fdf8 50%, #e8f5e8 75%, #f0f9f0 100%);
|
||||
background-size: 400% 400%;
|
||||
animation: gradientShift 15s ease infinite;
|
||||
position: relative;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
/* 背景渐变动画 */
|
||||
@keyframes gradientShift {
|
||||
0% {
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
50% {
|
||||
background-position: 100% 50%;
|
||||
}
|
||||
100% {
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
}
|
||||
|
||||
/* 装饰性背景元素 */
|
||||
body::before {
|
||||
content: '';
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image:
|
||||
radial-gradient(circle at 20% 80%, rgba(76, 175, 80, 0.1) 0%, transparent 50%),
|
||||
radial-gradient(circle at 80% 20%, rgba(45, 90, 61, 0.08) 0%, transparent 50%),
|
||||
radial-gradient(circle at 40% 40%, rgba(76, 175, 80, 0.05) 0%, transparent 50%);
|
||||
pointer-events: none;
|
||||
z-index: -2;
|
||||
}
|
||||
|
||||
/* 浮动装饰圆点 */
|
||||
body::after {
|
||||
content: '';
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image:
|
||||
radial-gradient(2px 2px at 20px 30px, rgba(76, 175, 80, 0.3), transparent),
|
||||
radial-gradient(2px 2px at 40px 70px, rgba(45, 90, 61, 0.2), transparent),
|
||||
radial-gradient(1px 1px at 90px 40px, rgba(76, 175, 80, 0.4), transparent),
|
||||
radial-gradient(1px 1px at 130px 80px, rgba(45, 90, 61, 0.3), transparent),
|
||||
radial-gradient(2px 2px at 160px 30px, rgba(76, 175, 80, 0.2), transparent);
|
||||
background-repeat: repeat;
|
||||
background-size: 200px 100px;
|
||||
animation: floatDots 20s linear infinite;
|
||||
pointer-events: none;
|
||||
z-index: -1;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
/* 圆点浮动动画 */
|
||||
@keyframes floatDots {
|
||||
0% {
|
||||
transform: translateY(0px) translateX(0px);
|
||||
}
|
||||
33% {
|
||||
transform: translateY(-10px) translateX(5px);
|
||||
}
|
||||
66% {
|
||||
transform: translateY(5px) translateX(-5px);
|
||||
}
|
||||
100% {
|
||||
transform: translateY(0px) translateX(0px);
|
||||
}
|
||||
}
|
||||
|
||||
/* 网格背景(可选,默认隐藏) */
|
||||
.grid-background {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image:
|
||||
linear-gradient(rgba(76, 175, 80, 0.03) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(76, 175, 80, 0.03) 1px, transparent 1px);
|
||||
background-size: 50px 50px;
|
||||
pointer-events: none;
|
||||
z-index: -3;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.grid-background.active {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* 响应式背景调整 */
|
||||
@media (max-width: 768px) {
|
||||
body::after {
|
||||
background-size: 150px 75px;
|
||||
animation-duration: 25s;
|
||||
}
|
||||
|
||||
body::before {
|
||||
background-image:
|
||||
radial-gradient(circle at 30% 70%, rgba(76, 175, 80, 0.08) 0%, transparent 50%),
|
||||
radial-gradient(circle at 70% 30%, rgba(45, 90, 61, 0.06) 0%, transparent 50%);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
body {
|
||||
animation-duration: 20s;
|
||||
}
|
||||
|
||||
body::after {
|
||||
background-size: 100px 50px;
|
||||
opacity: 0.4;
|
||||
}
|
||||
}
|
||||
|
||||
/* 高对比度模式下的背景调整 */
|
||||
@media (prefers-contrast: high) {
|
||||
body {
|
||||
background: #f8fdf8;
|
||||
animation: none;
|
||||
}
|
||||
|
||||
body::before,
|
||||
body::after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* 减少动画模式下的背景调整 */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
body {
|
||||
animation: none;
|
||||
background: linear-gradient(135deg, #e8f5e8 0%, #f0f9f0 50%, #f8fdf8 100%);
|
||||
}
|
||||
|
||||
body::after {
|
||||
animation: none;
|
||||
}
|
||||
|
||||
@keyframes gradientShift {
|
||||
0%, 100% {
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes floatDots {
|
||||
0%, 100% {
|
||||
transform: translateY(0px) translateX(0px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 深色模式支持 */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
background: linear-gradient(135deg, #1a2e1a 0%, #2d4a2d 25%, #1f3a1f 50%, #1a2e1a 75%, #2d4a2d 100%);
|
||||
}
|
||||
|
||||
body::before {
|
||||
background-image:
|
||||
radial-gradient(circle at 20% 80%, rgba(76, 175, 80, 0.15) 0%, transparent 50%),
|
||||
radial-gradient(circle at 80% 20%, rgba(144, 238, 144, 0.1) 0%, transparent 50%),
|
||||
radial-gradient(circle at 40% 40%, rgba(76, 175, 80, 0.08) 0%, transparent 50%);
|
||||
}
|
||||
|
||||
body::after {
|
||||
background-image:
|
||||
radial-gradient(2px 2px at 20px 30px, rgba(144, 238, 144, 0.4), transparent),
|
||||
radial-gradient(2px 2px at 40px 70px, rgba(76, 175, 80, 0.3), transparent),
|
||||
radial-gradient(1px 1px at 90px 40px, rgba(144, 238, 144, 0.5), transparent),
|
||||
radial-gradient(1px 1px at 130px 80px, rgba(76, 175, 80, 0.4), transparent),
|
||||
radial-gradient(2px 2px at 160px 30px, rgba(144, 238, 144, 0.3), transparent);
|
||||
}
|
||||
}
|
||||
|
||||
/* 打印样式 */
|
||||
@media print {
|
||||
body {
|
||||
background: white !important;
|
||||
animation: none !important;
|
||||
}
|
||||
|
||||
body::before,
|
||||
body::after {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* 特殊效果:鼠标悬停时的背景变化 */
|
||||
@media (hover: hover) {
|
||||
.container:hover {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.container:hover::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -20px;
|
||||
left: -20px;
|
||||
right: -20px;
|
||||
bottom: -20px;
|
||||
background: radial-gradient(circle at var(--mouse-x, 50%) var(--mouse-y, 50%), rgba(76, 175, 80, 0.05) 0%, transparent 50%);
|
||||
border-radius: 30px;
|
||||
pointer-events: none;
|
||||
z-index: -1;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
}
|
||||
|
||||
/* 季节性主题变化(可通过JavaScript控制) */
|
||||
.theme-spring body {
|
||||
background: linear-gradient(135deg, #e8f5e8 0%, #f0f9f0 25%, #e1f5e1 50%, #f8fdf8 75%, #e8f5e8 100%);
|
||||
}
|
||||
|
||||
.theme-summer body {
|
||||
background: linear-gradient(135deg, #f0f9f0 0%, #e8f5e8 25%, #f8fdf8 50%, #e1f5e1 75%, #f0f9f0 100%);
|
||||
}
|
||||
|
||||
.theme-autumn body {
|
||||
background: linear-gradient(135deg, #f5f0e8 0%, #f9f5f0 25%, #fdf8f0 50%, #f5f0e8 75%, #f9f5f0 100%);
|
||||
}
|
||||
|
||||
.theme-winter body {
|
||||
background: linear-gradient(135deg, #f0f5f8 0%, #f5f9fc 25%, #f8fbfd 50%, #f0f5f8 75%, #f5f9fc 100%);
|
||||
}
|
||||
|
||||
/* 性能优化:GPU加速 */
|
||||
body,
|
||||
body::before,
|
||||
body::after {
|
||||
will-change: transform;
|
||||
transform: translateZ(0);
|
||||
}
|
||||
|
||||
/* 无障碍支持:为屏幕阅读器隐藏装饰元素 */
|
||||
body::before,
|
||||
body::after {
|
||||
speak: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
@@ -1,647 +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: #2d5a3d;
|
||||
min-height: 100vh;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
/* 容器布局 */
|
||||
.container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 头部样式 */
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-bottom: 40px;
|
||||
padding: 30px 20px;
|
||||
background: linear-gradient(135deg, #e8f5e8 0%, #f0f9f0 100%);
|
||||
border-radius: 20px;
|
||||
box-shadow: 0 4px 20px rgba(45, 90, 61, 0.1);
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 2.5rem;
|
||||
font-weight: 700;
|
||||
color: #2d5a3d;
|
||||
margin-bottom: 10px;
|
||||
text-shadow: 0 2px 4px rgba(45, 90, 61, 0.1);
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 1.1rem;
|
||||
color: #5a8a6b;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
/* 主内容区域 */
|
||||
.main-content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 30px;
|
||||
}
|
||||
|
||||
/* 表单容器 */
|
||||
.form-container {
|
||||
background: #ffffff;
|
||||
border-radius: 16px;
|
||||
padding: 30px;
|
||||
box-shadow: 0 8px 32px rgba(45, 90, 61, 0.1);
|
||||
border: 1px solid #e8f5e8;
|
||||
}
|
||||
|
||||
.password-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 25px;
|
||||
}
|
||||
|
||||
/* 表单组样式 */
|
||||
.form-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
font-weight: 600;
|
||||
color: #2d5a3d;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 1.1rem;
|
||||
color: #2d5a3d;
|
||||
font-weight: 600;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
/* 长度控制 */
|
||||
.length-control {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
padding: 15px;
|
||||
background: #f8fdf8;
|
||||
border-radius: 12px;
|
||||
border: 2px solid #e8f5e8;
|
||||
}
|
||||
|
||||
.length-slider {
|
||||
flex: 1;
|
||||
height: 6px;
|
||||
background: #e8f5e8;
|
||||
border-radius: 3px;
|
||||
outline: none;
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
.length-slider::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background: linear-gradient(135deg, #4caf50, #45a049);
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 2px 8px rgba(76, 175, 80, 0.3);
|
||||
}
|
||||
|
||||
.length-slider::-moz-range-thumb {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background: linear-gradient(135deg, #4caf50, #45a049);
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
box-shadow: 0 2px 8px rgba(76, 175, 80, 0.3);
|
||||
}
|
||||
|
||||
.length-display {
|
||||
min-width: 40px;
|
||||
text-align: center;
|
||||
font-weight: 700;
|
||||
font-size: 1.2rem;
|
||||
color: #2d5a3d;
|
||||
background: #ffffff;
|
||||
padding: 8px 12px;
|
||||
border-radius: 8px;
|
||||
border: 2px solid #e8f5e8;
|
||||
}
|
||||
|
||||
/* 复选框组 */
|
||||
.checkbox-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.checkbox-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px 16px;
|
||||
background: #f8fdf8;
|
||||
border-radius: 10px;
|
||||
border: 2px solid #e8f5e8;
|
||||
transition: all 0.3s ease;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.checkbox-item:hover {
|
||||
background: #f0f9f0;
|
||||
border-color: #d4edda;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.checkbox-item input[type="checkbox"] {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
accent-color: #4caf50;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.checkbox-item label {
|
||||
flex: 1;
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
color: #2d5a3d;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* 生成按钮 */
|
||||
.generate-btn {
|
||||
background: linear-gradient(135deg, #4caf50, #45a049);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 16px 32px;
|
||||
border-radius: 12px;
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 4px 16px rgba(76, 175, 80, 0.3);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.generate-btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 20px rgba(76, 175, 80, 0.4);
|
||||
}
|
||||
|
||||
.generate-btn:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.generate-btn:disabled {
|
||||
opacity: 0.7;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
/* 结果容器 */
|
||||
.result-container {
|
||||
background: #ffffff;
|
||||
border-radius: 16px;
|
||||
padding: 30px;
|
||||
box-shadow: 0 8px 32px rgba(45, 90, 61, 0.1);
|
||||
border: 1px solid #e8f5e8;
|
||||
animation: slideIn 0.5s ease-out;
|
||||
}
|
||||
|
||||
@keyframes slideIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.result-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.result-header h3 {
|
||||
color: #2d5a3d;
|
||||
font-size: 1.3rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.copy-btn {
|
||||
background: #4caf50;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 10px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.copy-btn:hover {
|
||||
background: #45a049;
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
/* 密码显示 */
|
||||
.password-display {
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.password-input {
|
||||
width: 100%;
|
||||
padding: 16px 20px;
|
||||
border: 2px solid #e8f5e8;
|
||||
border-radius: 12px;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
color: #2d5a3d;
|
||||
background: #f8fdf8;
|
||||
text-align: center;
|
||||
letter-spacing: 1px;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.password-input:focus {
|
||||
outline: none;
|
||||
border-color: #4caf50;
|
||||
box-shadow: 0 0 0 3px rgba(76, 175, 80, 0.1);
|
||||
}
|
||||
|
||||
/* 密码信息 */
|
||||
.password-info {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||
gap: 15px;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
padding: 12px 16px;
|
||||
background: #f8fdf8;
|
||||
border-radius: 10px;
|
||||
border: 1px solid #e8f5e8;
|
||||
}
|
||||
|
||||
.info-item.full-width {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
font-size: 0.9rem;
|
||||
color: #5a8a6b;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
font-size: 1rem;
|
||||
color: #2d5a3d;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.info-value.strength {
|
||||
padding: 4px 8px;
|
||||
border-radius: 6px;
|
||||
text-align: center;
|
||||
color: white;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.strength.weak {
|
||||
background: #f44336;
|
||||
}
|
||||
|
||||
.strength.medium {
|
||||
background: #ff9800;
|
||||
}
|
||||
|
||||
.strength.strong {
|
||||
background: #4caf50;
|
||||
}
|
||||
|
||||
.strength.very-strong {
|
||||
background: #2e7d32;
|
||||
}
|
||||
|
||||
/* 字符集显示 */
|
||||
.character-sets {
|
||||
border-top: 1px solid #e8f5e8;
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
.character-sets h4 {
|
||||
color: #2d5a3d;
|
||||
margin-bottom: 15px;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.sets-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.set-item {
|
||||
background: #e8f5e8;
|
||||
color: #2d5a3d;
|
||||
padding: 6px 12px;
|
||||
border-radius: 20px;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 错误容器 */
|
||||
.error-container {
|
||||
background: #ffffff;
|
||||
border-radius: 16px;
|
||||
padding: 40px 30px;
|
||||
text-align: center;
|
||||
box-shadow: 0 8px 32px rgba(244, 67, 54, 0.1);
|
||||
border: 1px solid #ffebee;
|
||||
}
|
||||
|
||||
.error-icon {
|
||||
font-size: 3rem;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.error-container h3 {
|
||||
color: #d32f2f;
|
||||
margin-bottom: 10px;
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
|
||||
.error-container p {
|
||||
color: #666;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.retry-btn {
|
||||
background: #f44336;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 12px 24px;
|
||||
border-radius: 8px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.retry-btn:hover {
|
||||
background: #d32f2f;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
/* 页脚 */
|
||||
.footer {
|
||||
text-align: center;
|
||||
padding: 30px 20px;
|
||||
color: #5a8a6b;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
/* 提示框 */
|
||||
.toast {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
background: #4caf50;
|
||||
color: white;
|
||||
padding: 12px 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 12px rgba(76, 175, 80, 0.3);
|
||||
z-index: 1000;
|
||||
animation: toastSlide 0.3s ease-out;
|
||||
}
|
||||
|
||||
@keyframes toastSlide {
|
||||
from {
|
||||
transform: translateX(100%);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: translateX(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* 平板端适配 (768px - 1024px) */
|
||||
@media (min-width: 768px) and (max-width: 1024px) {
|
||||
.container {
|
||||
max-width: 700px;
|
||||
padding: 25px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 2.2rem;
|
||||
}
|
||||
|
||||
.form-container,
|
||||
.result-container {
|
||||
padding: 25px;
|
||||
}
|
||||
|
||||
.password-info {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
/* 手机端适配 (最大767px) */
|
||||
@media (max-width: 767px) {
|
||||
.container {
|
||||
padding: 15px;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: 20px 15px;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.form-container,
|
||||
.result-container {
|
||||
padding: 20px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.password-form {
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.length-control {
|
||||
padding: 12px;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.length-display {
|
||||
min-width: 35px;
|
||||
padding: 6px 10px;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.checkbox-item {
|
||||
padding: 10px 12px;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.checkbox-item input[type="checkbox"] {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.generate-btn {
|
||||
padding: 14px 28px;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.password-input {
|
||||
padding: 14px 16px;
|
||||
font-size: 1rem;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.password-info {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
padding: 10px 12px;
|
||||
}
|
||||
|
||||
.result-header {
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.copy-btn {
|
||||
align-self: center;
|
||||
padding: 12px 20px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.toast {
|
||||
right: 15px;
|
||||
left: 15px;
|
||||
top: 15px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
/* 小屏手机适配 (最大480px) */
|
||||
@media (max-width: 480px) {
|
||||
.container {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: 15px 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 1.6rem;
|
||||
}
|
||||
|
||||
.form-container,
|
||||
.result-container {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.checkbox-item {
|
||||
padding: 8px 10px;
|
||||
}
|
||||
|
||||
.generate-btn {
|
||||
padding: 12px 24px;
|
||||
}
|
||||
|
||||
.password-input {
|
||||
padding: 12px 14px;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* 触摸设备优化 */
|
||||
@media (hover: none) and (pointer: coarse) {
|
||||
.checkbox-item,
|
||||
.generate-btn,
|
||||
.copy-btn,
|
||||
.retry-btn {
|
||||
min-height: 44px;
|
||||
}
|
||||
|
||||
.checkbox-item input[type="checkbox"] {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.length-slider::-webkit-slider-thumb {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 高对比度模式支持 */
|
||||
@media (prefers-contrast: high) {
|
||||
.form-container,
|
||||
.result-container {
|
||||
border: 2px solid #2d5a3d;
|
||||
}
|
||||
|
||||
.checkbox-item {
|
||||
border: 1px solid #2d5a3d;
|
||||
}
|
||||
|
||||
.password-input {
|
||||
border: 2px solid #2d5a3d;
|
||||
}
|
||||
}
|
||||
|
||||
/* 减少动画模式支持 */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
* {
|
||||
animation-duration: 0.01ms !important;
|
||||
animation-iteration-count: 1 !important;
|
||||
transition-duration: 0.01ms !important;
|
||||
}
|
||||
}
|
||||
@@ -1,146 +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>
|
||||
<p class="subtitle">生成安全可靠的随机密码</p>
|
||||
</header>
|
||||
|
||||
<main class="main-content">
|
||||
<div class="form-container">
|
||||
<form id="passwordForm" class="password-form">
|
||||
<div class="form-group">
|
||||
<label for="length">密码长度</label>
|
||||
<div class="length-control">
|
||||
<input type="range" id="length" name="length" min="4" max="128" value="16" class="length-slider">
|
||||
<span id="lengthDisplay" class="length-display">16</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="section-title">字符类型</label>
|
||||
<div class="checkbox-group">
|
||||
<div class="checkbox-item">
|
||||
<input type="checkbox" id="numbers" name="numbers" checked>
|
||||
<label for="numbers">包含数字 (0-9)</label>
|
||||
</div>
|
||||
<div class="checkbox-item">
|
||||
<input type="checkbox" id="uppercase" name="uppercase" checked>
|
||||
<label for="uppercase">包含大写字母 (A-Z)</label>
|
||||
</div>
|
||||
<div class="checkbox-item">
|
||||
<input type="checkbox" id="lowercase" name="lowercase" checked>
|
||||
<label for="lowercase">包含小写字母 (a-z)</label>
|
||||
</div>
|
||||
<div class="checkbox-item">
|
||||
<input type="checkbox" id="symbols" name="symbols">
|
||||
<label for="symbols">包含特殊字符 (!@#$%^&*)</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="section-title">高级选项</label>
|
||||
<div class="checkbox-group">
|
||||
<div class="checkbox-item">
|
||||
<input type="checkbox" id="excludeSimilar" name="excludeSimilar" checked>
|
||||
<label for="excludeSimilar">排除相似字符 (0,O,l,1,I)</label>
|
||||
</div>
|
||||
<div class="checkbox-item">
|
||||
<input type="checkbox" id="excludeAmbiguous" name="excludeAmbiguous" checked>
|
||||
<label for="excludeAmbiguous">排除模糊字符</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="generate-btn" id="generateBtn">
|
||||
<span class="btn-text">生成密码</span>
|
||||
<span class="btn-loading" style="display: none;">生成中...</span>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="result-container" id="resultContainer" style="display: none;">
|
||||
<div class="result-header">
|
||||
<h3>生成的密码</h3>
|
||||
<button class="copy-btn" id="copyBtn" title="复制密码">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
|
||||
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="password-display">
|
||||
<input type="text" id="passwordResult" class="password-input" readonly>
|
||||
</div>
|
||||
|
||||
<div class="password-info">
|
||||
<div class="info-item">
|
||||
<span class="info-label">长度:</span>
|
||||
<span id="infoLength" class="info-value">-</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">强度:</span>
|
||||
<span id="infoStrength" class="info-value strength">-</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">熵值:</span>
|
||||
<span id="infoEntropy" class="info-value">-</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">数字:</span>
|
||||
<span id="infoNumbers" class="info-value">-</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">大写:</span>
|
||||
<span id="infoUppercase" class="info-value">-</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">小写:</span>
|
||||
<span id="infoLowercase" class="info-value">-</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">符号:</span>
|
||||
<span id="infoSymbols" class="info-value">-</span>
|
||||
</div>
|
||||
<div class="info-item full-width">
|
||||
<span class="info-label">破解时间:</span>
|
||||
<span id="infoCrackTime" class="info-value">-</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="character-sets" id="characterSets">
|
||||
<h4>使用的字符集</h4>
|
||||
<div class="sets-list" id="setsList"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="error-container" id="errorContainer" style="display: none;">
|
||||
<div class="error-icon">⚠️</div>
|
||||
<h3>生成失败</h3>
|
||||
<p id="errorMessage">请检查网络连接后重试</p>
|
||||
<button class="retry-btn" id="retryBtn">重新生成</button>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="footer">
|
||||
<p>安全密码生成工具</p>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<div class="toast" id="toast" style="display: none;">
|
||||
<span id="toastMessage">密码已复制到剪贴板</span>
|
||||
</div>
|
||||
|
||||
<script src="js/script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,412 +0,0 @@
|
||||
class PasswordGenerator {
|
||||
constructor() {
|
||||
this.apiUrl = 'https://60s.api.shumengya.top/v2/password';
|
||||
this.loadStartTime = 0;
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
this.bindEvents();
|
||||
this.updateLengthDisplay();
|
||||
this.preloadResources();
|
||||
}
|
||||
|
||||
preloadResources() {
|
||||
// 预连接API服务器
|
||||
const link = document.createElement('link');
|
||||
link.rel = 'preconnect';
|
||||
link.href = 'https://60s.api.shumengya.top';
|
||||
document.head.appendChild(link);
|
||||
}
|
||||
|
||||
bindEvents() {
|
||||
// 长度滑块事件
|
||||
const lengthSlider = document.getElementById('length');
|
||||
lengthSlider.addEventListener('input', () => this.updateLengthDisplay());
|
||||
|
||||
// 生成按钮事件
|
||||
const generateBtn = document.getElementById('generateBtn');
|
||||
generateBtn.addEventListener('click', () => this.generatePassword());
|
||||
|
||||
// 复制按钮事件
|
||||
const copyBtn = document.getElementById('copyBtn');
|
||||
copyBtn.addEventListener('click', () => this.copyPassword());
|
||||
|
||||
// 重试按钮事件
|
||||
const retryBtn = document.getElementById('retryBtn');
|
||||
retryBtn.addEventListener('click', () => this.generatePassword());
|
||||
|
||||
// 复选框变化事件
|
||||
const checkboxes = document.querySelectorAll('input[type="checkbox"]');
|
||||
checkboxes.forEach(checkbox => {
|
||||
checkbox.addEventListener('change', () => this.validateForm());
|
||||
});
|
||||
|
||||
// 键盘快捷键
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.ctrlKey && e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
this.generatePassword();
|
||||
}
|
||||
if (e.ctrlKey && e.key === 'c' && document.activeElement.id === 'passwordResult') {
|
||||
this.copyPassword();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
updateLengthDisplay() {
|
||||
const lengthSlider = document.getElementById('length');
|
||||
const lengthDisplay = document.getElementById('lengthDisplay');
|
||||
lengthDisplay.textContent = lengthSlider.value;
|
||||
}
|
||||
|
||||
validateForm() {
|
||||
const checkboxes = document.querySelectorAll('input[type="checkbox"]:checked');
|
||||
const generateBtn = document.getElementById('generateBtn');
|
||||
|
||||
// 至少需要选择一种字符类型
|
||||
const hasCharacterType = Array.from(checkboxes).some(cb =>
|
||||
['numbers', 'uppercase', 'lowercase', 'symbols'].includes(cb.id)
|
||||
);
|
||||
|
||||
generateBtn.disabled = !hasCharacterType;
|
||||
|
||||
if (!hasCharacterType) {
|
||||
this.showToast('请至少选择一种字符类型', 'warning');
|
||||
}
|
||||
}
|
||||
|
||||
async generatePassword() {
|
||||
this.loadStartTime = Date.now();
|
||||
|
||||
try {
|
||||
this.showLoading(true);
|
||||
this.hideError();
|
||||
|
||||
const params = this.getFormParams();
|
||||
const password = await this.callAPI(params);
|
||||
|
||||
if (password) {
|
||||
this.displayPassword(password, params);
|
||||
this.showToast('密码生成成功!', 'success');
|
||||
|
||||
const loadTime = Date.now() - this.loadStartTime;
|
||||
console.log(`密码生成完成,耗时: ${loadTime}ms`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('生成密码失败:', error);
|
||||
this.showError(error.message || '生成密码时发生错误,请重试');
|
||||
} finally {
|
||||
this.showLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
getFormParams() {
|
||||
const length = document.getElementById('length').value;
|
||||
const numbers = document.getElementById('numbers').checked;
|
||||
const uppercase = document.getElementById('uppercase').checked;
|
||||
const lowercase = document.getElementById('lowercase').checked;
|
||||
const symbols = document.getElementById('symbols').checked;
|
||||
const excludeSimilar = document.getElementById('excludeSimilar').checked;
|
||||
const excludeAmbiguous = document.getElementById('excludeAmbiguous').checked;
|
||||
|
||||
return {
|
||||
length: parseInt(length),
|
||||
numbers: numbers ? 'true' : 'false',
|
||||
uppercase: uppercase ? 'true' : 'false',
|
||||
lowercase: lowercase ? 'true' : 'false',
|
||||
symbols: symbols ? 'true' : 'false',
|
||||
exclude_similar: excludeSimilar ? 'true' : 'false',
|
||||
exclude_ambiguous: excludeAmbiguous ? 'true' : 'false',
|
||||
encoding: 'json'
|
||||
};
|
||||
}
|
||||
|
||||
async callAPI(params) {
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), 5000);
|
||||
|
||||
try {
|
||||
const url = new URL(this.apiUrl);
|
||||
Object.keys(params).forEach(key => {
|
||||
if (params[key] !== undefined && params[key] !== null) {
|
||||
url.searchParams.append(key, params[key]);
|
||||
}
|
||||
});
|
||||
|
||||
const response = await fetch(url.toString(), {
|
||||
method: 'GET',
|
||||
signal: controller.signal,
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'User-Agent': 'PasswordGenerator/1.0'
|
||||
}
|
||||
});
|
||||
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.code === 200 && data.data && data.data.password) {
|
||||
return data.data.password;
|
||||
} else {
|
||||
throw new Error(data.message || '服务器返回了无效的密码数据');
|
||||
}
|
||||
} catch (error) {
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
if (error.name === 'AbortError') {
|
||||
throw new Error('请求超时,请检查网络连接后重试');
|
||||
}
|
||||
|
||||
if (error.message.includes('Failed to fetch')) {
|
||||
throw new Error('网络连接失败,请检查网络后重试');
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
displayPassword(password, params) {
|
||||
// 显示结果容器
|
||||
const resultContainer = document.getElementById('resultContainer');
|
||||
const errorContainer = document.getElementById('errorContainer');
|
||||
|
||||
resultContainer.style.display = 'block';
|
||||
errorContainer.style.display = 'none';
|
||||
|
||||
// 设置密码
|
||||
const passwordInput = document.getElementById('passwordResult');
|
||||
passwordInput.value = password;
|
||||
|
||||
// 计算并显示密码信息
|
||||
this.updatePasswordInfo(password, params);
|
||||
|
||||
// 滚动到结果区域
|
||||
resultContainer.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
}
|
||||
|
||||
updatePasswordInfo(password, params) {
|
||||
// 基本信息
|
||||
document.getElementById('infoLength').textContent = password.length;
|
||||
document.getElementById('infoEntropy').textContent = this.calculateEntropy(password).toFixed(1);
|
||||
|
||||
// 密码强度
|
||||
const strength = this.calculateStrength(password);
|
||||
const strengthElement = document.getElementById('infoStrength');
|
||||
strengthElement.textContent = strength.text;
|
||||
strengthElement.className = `info-value strength ${strength.class}`;
|
||||
|
||||
// 字符类型统计
|
||||
const stats = this.analyzeCharacters(password);
|
||||
document.getElementById('infoNumbers').textContent = stats.numbers;
|
||||
document.getElementById('infoUppercase').textContent = stats.uppercase;
|
||||
document.getElementById('infoLowercase').textContent = stats.lowercase;
|
||||
document.getElementById('infoSymbols').textContent = stats.symbols;
|
||||
|
||||
// 使用的字符集
|
||||
this.updateCharacterSets(params);
|
||||
|
||||
// 破解时间估算
|
||||
document.getElementById('infoCrackTime').textContent = this.estimateCrackTime(password);
|
||||
}
|
||||
|
||||
calculateEntropy(password) {
|
||||
const charset = this.getCharsetSize(password);
|
||||
return Math.log2(Math.pow(charset, password.length));
|
||||
}
|
||||
|
||||
getCharsetSize(password) {
|
||||
let size = 0;
|
||||
if (/[0-9]/.test(password)) size += 10;
|
||||
if (/[a-z]/.test(password)) size += 26;
|
||||
if (/[A-Z]/.test(password)) size += 26;
|
||||
if (/[^a-zA-Z0-9]/.test(password)) size += 32;
|
||||
return size;
|
||||
}
|
||||
|
||||
calculateStrength(password) {
|
||||
const entropy = this.calculateEntropy(password);
|
||||
|
||||
if (entropy < 30) {
|
||||
return { text: '弱', class: 'weak' };
|
||||
} else if (entropy < 50) {
|
||||
return { text: '中等', class: 'medium' };
|
||||
} else if (entropy < 70) {
|
||||
return { text: '强', class: 'strong' };
|
||||
} else {
|
||||
return { text: '非常强', class: 'very-strong' };
|
||||
}
|
||||
}
|
||||
|
||||
analyzeCharacters(password) {
|
||||
return {
|
||||
numbers: (password.match(/[0-9]/g) || []).length,
|
||||
uppercase: (password.match(/[A-Z]/g) || []).length,
|
||||
lowercase: (password.match(/[a-z]/g) || []).length,
|
||||
symbols: (password.match(/[^a-zA-Z0-9]/g) || []).length
|
||||
};
|
||||
}
|
||||
|
||||
updateCharacterSets(params) {
|
||||
const setsList = document.getElementById('setsList');
|
||||
const sets = [];
|
||||
|
||||
if (params.numbers === 'true') sets.push('数字 (0-9)');
|
||||
if (params.uppercase === 'true') sets.push('大写字母 (A-Z)');
|
||||
if (params.lowercase === 'true') sets.push('小写字母 (a-z)');
|
||||
if (params.symbols === 'true') sets.push('特殊字符 (!@#$...)');
|
||||
|
||||
setsList.innerHTML = sets.map(set => `<span class="set-item">${set}</span>`).join('');
|
||||
}
|
||||
|
||||
estimateCrackTime(password) {
|
||||
const charset = this.getCharsetSize(password);
|
||||
const combinations = Math.pow(charset, password.length);
|
||||
const guessesPerSecond = 1e9; // 假设每秒10亿次尝试
|
||||
const secondsToCrack = combinations / (2 * guessesPerSecond);
|
||||
|
||||
if (secondsToCrack < 60) {
|
||||
return '不到1分钟';
|
||||
} else if (secondsToCrack < 3600) {
|
||||
return `${Math.ceil(secondsToCrack / 60)}分钟`;
|
||||
} else if (secondsToCrack < 86400) {
|
||||
return `${Math.ceil(secondsToCrack / 3600)}小时`;
|
||||
} else if (secondsToCrack < 31536000) {
|
||||
return `${Math.ceil(secondsToCrack / 86400)}天`;
|
||||
} else if (secondsToCrack < 31536000000) {
|
||||
return `${Math.ceil(secondsToCrack / 31536000)}年`;
|
||||
} else {
|
||||
return '数千年以上';
|
||||
}
|
||||
}
|
||||
|
||||
async copyPassword() {
|
||||
const passwordInput = document.getElementById('passwordResult');
|
||||
|
||||
try {
|
||||
if (navigator.clipboard && window.isSecureContext) {
|
||||
await navigator.clipboard.writeText(passwordInput.value);
|
||||
} else {
|
||||
// 降级方案
|
||||
passwordInput.select();
|
||||
passwordInput.setSelectionRange(0, 99999);
|
||||
document.execCommand('copy');
|
||||
}
|
||||
|
||||
this.showToast('密码已复制到剪贴板!', 'success');
|
||||
|
||||
// 复制按钮反馈
|
||||
const copyBtn = document.getElementById('copyBtn');
|
||||
const originalText = copyBtn.innerHTML;
|
||||
copyBtn.innerHTML = '✓ 已复制';
|
||||
copyBtn.style.background = '#2e7d32';
|
||||
|
||||
setTimeout(() => {
|
||||
copyBtn.innerHTML = originalText;
|
||||
copyBtn.style.background = '';
|
||||
}, 2000);
|
||||
|
||||
} catch (error) {
|
||||
console.error('复制失败:', error);
|
||||
this.showToast('复制失败,请手动选择密码', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
showLoading(show) {
|
||||
const generateBtn = document.getElementById('generateBtn');
|
||||
|
||||
if (show) {
|
||||
generateBtn.disabled = true;
|
||||
generateBtn.innerHTML = '<span style="display: inline-block; animation: spin 1s linear infinite;">⟳</span> 生成中...';
|
||||
} else {
|
||||
generateBtn.disabled = false;
|
||||
generateBtn.innerHTML = '🔐 生成密码';
|
||||
}
|
||||
}
|
||||
|
||||
showError(message) {
|
||||
const errorContainer = document.getElementById('errorContainer');
|
||||
const resultContainer = document.getElementById('resultContainer');
|
||||
const errorMessage = document.getElementById('errorMessage');
|
||||
|
||||
errorMessage.textContent = message;
|
||||
errorContainer.style.display = 'block';
|
||||
resultContainer.style.display = 'none';
|
||||
|
||||
errorContainer.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
}
|
||||
|
||||
hideError() {
|
||||
const errorContainer = document.getElementById('errorContainer');
|
||||
errorContainer.style.display = 'none';
|
||||
}
|
||||
|
||||
showToast(message, type = 'info') {
|
||||
// 移除现有的toast
|
||||
const existingToast = document.querySelector('.toast');
|
||||
if (existingToast) {
|
||||
existingToast.remove();
|
||||
}
|
||||
|
||||
const toast = document.createElement('div');
|
||||
toast.className = 'toast';
|
||||
toast.textContent = message;
|
||||
|
||||
// 根据类型设置颜色
|
||||
const colors = {
|
||||
success: '#4caf50',
|
||||
error: '#f44336',
|
||||
warning: '#ff9800',
|
||||
info: '#2196f3'
|
||||
};
|
||||
|
||||
toast.style.background = colors[type] || colors.info;
|
||||
|
||||
document.body.appendChild(toast);
|
||||
|
||||
// 3秒后自动移除
|
||||
setTimeout(() => {
|
||||
if (toast.parentNode) {
|
||||
toast.remove();
|
||||
}
|
||||
}, 3000);
|
||||
}
|
||||
}
|
||||
|
||||
// 添加旋转动画样式
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
@keyframes spin {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
|
||||
// 页面加载完成后初始化
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
new PasswordGenerator();
|
||||
});
|
||||
|
||||
// 页面可见性变化时的处理
|
||||
document.addEventListener('visibilitychange', () => {
|
||||
if (document.visibilityState === 'visible') {
|
||||
// 页面重新可见时,可以进行一些刷新操作
|
||||
console.log('页面重新可见');
|
||||
}
|
||||
});
|
||||
|
||||
// 错误处理
|
||||
window.addEventListener('error', (event) => {
|
||||
console.error('全局错误:', event.error);
|
||||
});
|
||||
|
||||
window.addEventListener('unhandledrejection', (event) => {
|
||||
console.error('未处理的Promise拒绝:', event.reason);
|
||||
event.preventDefault();
|
||||
});
|
||||
@@ -1,32 +0,0 @@
|
||||
{
|
||||
"code": 200,
|
||||
"message": "获取成功。数据来自官方/权威源头,以确保稳定与实时。开源地址 https://github.com/vikiboss/60s,反馈群 595941841",
|
||||
"data": {
|
||||
"password": "8mr2M7dZ6E3saj3F",
|
||||
"length": 16,
|
||||
"config": {
|
||||
"include_numbers": true,
|
||||
"include_symbols": false,
|
||||
"include_lowercase": true,
|
||||
"include_uppercase": true,
|
||||
"exclude_similar": true,
|
||||
"exclude_ambiguous": true
|
||||
},
|
||||
"character_sets": {
|
||||
"lowercase": "abcdefghjkmnpqrstuvwxyz",
|
||||
"uppercase": "ABCDEFGHIJKMNPQRSTUVWXYZ",
|
||||
"numbers": "23456789",
|
||||
"symbols": "",
|
||||
"used_sets": [
|
||||
"lowercase",
|
||||
"uppercase",
|
||||
"numbers"
|
||||
]
|
||||
},
|
||||
"generation_info": {
|
||||
"entropy": 92.5,
|
||||
"strength": "极强",
|
||||
"time_to_crack": "数百万年"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,215 +0,0 @@
|
||||
/* 背景样式文件 - 独立管理背景相关样式 */
|
||||
|
||||
/* 主体背景 */
|
||||
body {
|
||||
background: linear-gradient(135deg, #e8f5e8 0%, #f0fff0 50%, #e8f5e8 100%);
|
||||
background-attachment: fixed;
|
||||
background-size: 400% 400%;
|
||||
animation: gradientShift 15s ease infinite;
|
||||
}
|
||||
|
||||
/* 背景动画 */
|
||||
@keyframes gradientShift {
|
||||
0% {
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
50% {
|
||||
background-position: 100% 50%;
|
||||
}
|
||||
100% {
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
}
|
||||
|
||||
/* 容器背景装饰 */
|
||||
.container::before {
|
||||
content: '';
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image:
|
||||
radial-gradient(circle at 20% 80%, rgba(144, 205, 144, 0.1) 0%, transparent 50%),
|
||||
radial-gradient(circle at 80% 20%, rgba(45, 90, 39, 0.05) 0%, transparent 50%),
|
||||
radial-gradient(circle at 40% 40%, rgba(144, 205, 144, 0.08) 0%, transparent 50%);
|
||||
pointer-events: none;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
/* 输入区域背景 */
|
||||
.input-section {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
backdrop-filter: blur(10px);
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
/* 结果区域背景 */
|
||||
.result-section {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
backdrop-filter: blur(10px);
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
/* 格式组背景 */
|
||||
.format-group {
|
||||
background: rgba(248, 255, 248, 0.8);
|
||||
backdrop-filter: blur(5px);
|
||||
-webkit-backdrop-filter: blur(5px);
|
||||
}
|
||||
|
||||
/* 属性项背景 */
|
||||
.property-item {
|
||||
background: rgba(248, 255, 248, 0.8);
|
||||
backdrop-filter: blur(5px);
|
||||
-webkit-backdrop-filter: blur(5px);
|
||||
}
|
||||
|
||||
/* 调色板项背景 */
|
||||
.palette-item {
|
||||
background: rgba(248, 255, 248, 0.8);
|
||||
backdrop-filter: blur(5px);
|
||||
-webkit-backdrop-filter: blur(5px);
|
||||
}
|
||||
|
||||
/* 无障碍项背景 */
|
||||
.accessibility-item {
|
||||
background: rgba(248, 255, 248, 0.8);
|
||||
backdrop-filter: blur(5px);
|
||||
-webkit-backdrop-filter: blur(5px);
|
||||
}
|
||||
|
||||
/* 颜色预览背景 */
|
||||
.color-preview {
|
||||
background: rgba(248, 255, 248, 0.6);
|
||||
backdrop-filter: blur(8px);
|
||||
-webkit-backdrop-filter: blur(8px);
|
||||
}
|
||||
|
||||
/* 输入框背景 */
|
||||
.input-group input,
|
||||
.input-group select {
|
||||
background: rgba(248, 255, 248, 0.9);
|
||||
backdrop-filter: blur(5px);
|
||||
-webkit-backdrop-filter: blur(5px);
|
||||
}
|
||||
|
||||
.input-group input:focus,
|
||||
.input-group select:focus {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
}
|
||||
|
||||
/* 格式组内部元素背景 */
|
||||
.format-group p {
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
backdrop-filter: blur(3px);
|
||||
-webkit-backdrop-filter: blur(3px);
|
||||
}
|
||||
|
||||
/* 手机端背景优化 */
|
||||
@media (max-width: 767px) {
|
||||
body {
|
||||
background: linear-gradient(180deg, #e8f5e8 0%, #f0fff0 50%, #e8f5e8 100%);
|
||||
background-attachment: scroll; /* 手机端使用scroll避免性能问题 */
|
||||
}
|
||||
|
||||
.container::before {
|
||||
background-image:
|
||||
radial-gradient(circle at 30% 70%, rgba(144, 205, 144, 0.08) 0%, transparent 40%),
|
||||
radial-gradient(circle at 70% 30%, rgba(45, 90, 39, 0.04) 0%, transparent 40%);
|
||||
}
|
||||
|
||||
/* 减少手机端的模糊效果以提升性能 */
|
||||
.input-section,
|
||||
.result-section {
|
||||
backdrop-filter: blur(5px);
|
||||
-webkit-backdrop-filter: blur(5px);
|
||||
}
|
||||
|
||||
.format-group,
|
||||
.property-item,
|
||||
.palette-item,
|
||||
.accessibility-item {
|
||||
backdrop-filter: blur(3px);
|
||||
-webkit-backdrop-filter: blur(3px);
|
||||
}
|
||||
}
|
||||
|
||||
/* 平板端背景优化 */
|
||||
@media (min-width: 768px) and (max-width: 1024px) {
|
||||
.container::before {
|
||||
background-image:
|
||||
radial-gradient(circle at 25% 75%, rgba(144, 205, 144, 0.12) 0%, transparent 60%),
|
||||
radial-gradient(circle at 75% 25%, rgba(45, 90, 39, 0.06) 0%, transparent 60%),
|
||||
radial-gradient(circle at 50% 50%, rgba(144, 205, 144, 0.04) 0%, transparent 40%);
|
||||
}
|
||||
}
|
||||
|
||||
/* 电脑端背景优化 */
|
||||
@media (min-width: 1025px) {
|
||||
body {
|
||||
background-size: 300% 300%;
|
||||
animation-duration: 20s;
|
||||
}
|
||||
|
||||
.container::before {
|
||||
background-image:
|
||||
radial-gradient(circle at 15% 85%, rgba(144, 205, 144, 0.15) 0%, transparent 70%),
|
||||
radial-gradient(circle at 85% 15%, rgba(45, 90, 39, 0.08) 0%, transparent 70%),
|
||||
radial-gradient(circle at 35% 35%, rgba(144, 205, 144, 0.1) 0%, transparent 50%),
|
||||
radial-gradient(circle at 65% 65%, rgba(45, 90, 39, 0.05) 0%, transparent 50%);
|
||||
}
|
||||
|
||||
/* 电脑端增强模糊效果 */
|
||||
.input-section,
|
||||
.result-section {
|
||||
backdrop-filter: blur(15px);
|
||||
-webkit-backdrop-filter: blur(15px);
|
||||
}
|
||||
|
||||
.format-group,
|
||||
.property-item,
|
||||
.palette-item,
|
||||
.accessibility-item {
|
||||
backdrop-filter: blur(8px);
|
||||
-webkit-backdrop-filter: blur(8px);
|
||||
}
|
||||
}
|
||||
|
||||
/* 深色模式支持(如果用户系统设置为深色模式) */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
background: linear-gradient(135deg, #1a2e1a 0%, #0f1f0f 50%, #1a2e1a 100%);
|
||||
}
|
||||
|
||||
.container::before {
|
||||
background-image:
|
||||
radial-gradient(circle at 20% 80%, rgba(144, 205, 144, 0.05) 0%, transparent 50%),
|
||||
radial-gradient(circle at 80% 20%, rgba(45, 90, 39, 0.03) 0%, transparent 50%);
|
||||
}
|
||||
|
||||
.input-section,
|
||||
.result-section {
|
||||
background: rgba(26, 46, 26, 0.9);
|
||||
}
|
||||
|
||||
.format-group,
|
||||
.property-item,
|
||||
.palette-item,
|
||||
.accessibility-item,
|
||||
.color-preview {
|
||||
background: rgba(26, 46, 26, 0.6);
|
||||
}
|
||||
|
||||
.input-group input,
|
||||
.input-group select {
|
||||
background: rgba(26, 46, 26, 0.8);
|
||||
color: #e8f5e8;
|
||||
border-color: rgba(144, 205, 144, 0.3);
|
||||
}
|
||||
|
||||
.format-group p {
|
||||
background: rgba(15, 31, 15, 0.8);
|
||||
color: #e8f5e8;
|
||||
}
|
||||
}
|
||||
@@ -1,187 +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="styles.css">
|
||||
<link rel="stylesheet" href="background.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<header class="header">
|
||||
<h1>随机颜色/颜色转换工具</h1>
|
||||
<p class="subtitle">获取随机颜色或转换指定颜色格式</p>
|
||||
</header>
|
||||
|
||||
<main class="main-content">
|
||||
<div class="input-section">
|
||||
<div class="input-group">
|
||||
<label for="colorInput">输入颜色值(可选):</label>
|
||||
<input type="text" id="colorInput" placeholder="例如: #33AAFF">
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label for="encodingSelect">输出格式:</label>
|
||||
<select id="encodingSelect">
|
||||
<option value="json">JSON</option>
|
||||
<option value="text">文本</option>
|
||||
<option value="html">HTML</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="button-group">
|
||||
<button id="randomBtn" class="btn btn-primary">获取随机颜色</button>
|
||||
<button id="convertBtn" class="btn btn-secondary">转换颜色</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="result-section">
|
||||
<div class="color-preview">
|
||||
<div id="colorDisplay" class="color-box"></div>
|
||||
<div class="color-info">
|
||||
<h3 id="colorName">颜色名称</h3>
|
||||
<p id="hexValue">#000000</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="color-formats">
|
||||
<div class="format-group">
|
||||
<h4>RGB</h4>
|
||||
<div class="format-values">
|
||||
<span id="rgbR">0</span>
|
||||
<span id="rgbG">0</span>
|
||||
<span id="rgbB">0</span>
|
||||
</div>
|
||||
<p id="rgbString">rgb(0, 0, 0)</p>
|
||||
</div>
|
||||
|
||||
<div class="format-group">
|
||||
<h4>HSL</h4>
|
||||
<div class="format-values">
|
||||
<span id="hslH">0°</span>
|
||||
<span id="hslS">0%</span>
|
||||
<span id="hslL">0%</span>
|
||||
</div>
|
||||
<p id="hslString">hsl(0, 0%, 0%)</p>
|
||||
</div>
|
||||
|
||||
<div class="format-group">
|
||||
<h4>HSV</h4>
|
||||
<div class="format-values">
|
||||
<span id="hsvH">0°</span>
|
||||
<span id="hsvS">0%</span>
|
||||
<span id="hsvV">0%</span>
|
||||
</div>
|
||||
<p id="hsvString">hsv(0, 0%, 0%)</p>
|
||||
</div>
|
||||
|
||||
<div class="format-group">
|
||||
<h4>CMYK</h4>
|
||||
<div class="format-values">
|
||||
<span id="cmykC">0%</span>
|
||||
<span id="cmykM">0%</span>
|
||||
<span id="cmykY">0%</span>
|
||||
<span id="cmykK">0%</span>
|
||||
</div>
|
||||
<p id="cmykString">cmyk(0%, 0%, 0%, 0%)</p>
|
||||
</div>
|
||||
|
||||
<div class="format-group">
|
||||
<h4>LAB</h4>
|
||||
<div class="format-values">
|
||||
<span id="labL">0</span>
|
||||
<span id="labA">0</span>
|
||||
<span id="labB">0</span>
|
||||
</div>
|
||||
<p id="labString">lab(0, 0, 0)</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="color-properties">
|
||||
<div class="property-item">
|
||||
<label>亮度:</label>
|
||||
<span id="brightness">0</span>
|
||||
</div>
|
||||
<div class="property-item">
|
||||
<label>对比度 (白色):</label>
|
||||
<span id="contrastWhite">0</span>
|
||||
</div>
|
||||
<div class="property-item">
|
||||
<label>对比度 (黑色):</label>
|
||||
<span id="contrastBlack">0</span>
|
||||
</div>
|
||||
<div class="property-item">
|
||||
<label>最佳文字颜色:</label>
|
||||
<span id="bestTextColor">#000000</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="color-palette">
|
||||
<h4>配色方案</h4>
|
||||
<div class="palette-group">
|
||||
<div class="palette-item">
|
||||
<label>互补色:</label>
|
||||
<div id="complementary" class="color-sample"></div>
|
||||
<span id="complementaryHex">#000000</span>
|
||||
</div>
|
||||
<div class="palette-item">
|
||||
<label>类似色:</label>
|
||||
<div class="analogous-colors">
|
||||
<div id="analogous1" class="color-sample"></div>
|
||||
<div id="analogous2" class="color-sample"></div>
|
||||
</div>
|
||||
<div class="analogous-hex">
|
||||
<span id="analogous1Hex">#000000</span>
|
||||
<span id="analogous2Hex">#000000</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="palette-item">
|
||||
<label>三角色:</label>
|
||||
<div class="triadic-colors">
|
||||
<div id="triadic1" class="color-sample"></div>
|
||||
<div id="triadic2" class="color-sample"></div>
|
||||
</div>
|
||||
<div class="triadic-hex">
|
||||
<span id="triadic1Hex">#000000</span>
|
||||
<span id="triadic2Hex">#000000</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="accessibility-info">
|
||||
<h4>无障碍性</h4>
|
||||
<div class="accessibility-grid">
|
||||
<div class="accessibility-item">
|
||||
<span>AA 普通文本:</span>
|
||||
<span id="aaNormal" class="status">否</span>
|
||||
</div>
|
||||
<div class="accessibility-item">
|
||||
<span>AA 大文本:</span>
|
||||
<span id="aaLarge" class="status">否</span>
|
||||
</div>
|
||||
<div class="accessibility-item">
|
||||
<span>AAA 普通文本:</span>
|
||||
<span id="aaaNormal" class="status">否</span>
|
||||
</div>
|
||||
<div class="accessibility-item">
|
||||
<span>AAA 大文本:</span>
|
||||
<span id="aaaLarge" class="status">否</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="loading" id="loading" style="display: none;">
|
||||
<div class="spinner"></div>
|
||||
<p>正在获取颜色信息...</p>
|
||||
</div>
|
||||
|
||||
<div class="error" id="error" style="display: none;">
|
||||
<p id="errorMessage">获取颜色信息失败,请稍后重试</p>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,426 +0,0 @@
|
||||
// 随机颜色/颜色转换工具 JavaScript
|
||||
|
||||
class ColorTool {
|
||||
constructor() {
|
||||
this.apiUrl = 'https://60s.api.shumengya.top/v2/color';
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
this.bindEvents();
|
||||
this.hideResultSection();
|
||||
}
|
||||
|
||||
bindEvents() {
|
||||
const randomBtn = document.getElementById('randomBtn');
|
||||
const convertBtn = document.getElementById('convertBtn');
|
||||
const colorInput = document.getElementById('colorInput');
|
||||
|
||||
randomBtn.addEventListener('click', () => this.getRandomColor());
|
||||
convertBtn.addEventListener('click', () => this.convertColor());
|
||||
|
||||
// 回车键支持
|
||||
colorInput.addEventListener('keypress', (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
this.convertColor();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
hideResultSection() {
|
||||
const resultSection = document.querySelector('.result-section');
|
||||
resultSection.style.display = 'none';
|
||||
}
|
||||
|
||||
showResultSection() {
|
||||
const resultSection = document.querySelector('.result-section');
|
||||
resultSection.style.display = 'block';
|
||||
}
|
||||
|
||||
showLoading() {
|
||||
const loading = document.getElementById('loading');
|
||||
const error = document.getElementById('error');
|
||||
loading.style.display = 'block';
|
||||
error.style.display = 'none';
|
||||
this.hideResultSection();
|
||||
}
|
||||
|
||||
hideLoading() {
|
||||
const loading = document.getElementById('loading');
|
||||
loading.style.display = 'none';
|
||||
}
|
||||
|
||||
showError(message) {
|
||||
const error = document.getElementById('error');
|
||||
const errorMessage = document.getElementById('errorMessage');
|
||||
const loading = document.getElementById('loading');
|
||||
|
||||
loading.style.display = 'none';
|
||||
errorMessage.textContent = message;
|
||||
error.style.display = 'block';
|
||||
this.hideResultSection();
|
||||
}
|
||||
|
||||
hideError() {
|
||||
const error = document.getElementById('error');
|
||||
error.style.display = 'none';
|
||||
}
|
||||
|
||||
async getRandomColor() {
|
||||
try {
|
||||
this.showLoading();
|
||||
const encoding = document.getElementById('encodingSelect').value;
|
||||
const url = `${this.apiUrl}?encoding=${encoding}`;
|
||||
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.code === 200) {
|
||||
this.displayColorData(data.data);
|
||||
this.hideLoading();
|
||||
this.hideError();
|
||||
this.showResultSection();
|
||||
} else {
|
||||
throw new Error(data.message || '获取颜色信息失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取随机颜色失败:', error);
|
||||
this.showError(`获取随机颜色失败: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
async convertColor() {
|
||||
const colorInput = document.getElementById('colorInput');
|
||||
const colorValue = colorInput.value.trim();
|
||||
|
||||
if (!colorValue) {
|
||||
this.showError('请输入要转换的颜色值');
|
||||
return;
|
||||
}
|
||||
|
||||
// 简单的颜色格式验证
|
||||
if (!this.isValidColor(colorValue)) {
|
||||
this.showError('请输入有效的颜色值(如 #33AAFF)');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.showLoading();
|
||||
const encoding = document.getElementById('encodingSelect').value;
|
||||
const url = `${this.apiUrl}?color=${encodeURIComponent(colorValue)}&encoding=${encoding}`;
|
||||
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.code === 200) {
|
||||
this.displayColorData(data.data);
|
||||
this.hideLoading();
|
||||
this.hideError();
|
||||
this.showResultSection();
|
||||
} else {
|
||||
throw new Error(data.message || '转换颜色失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('转换颜色失败:', error);
|
||||
this.showError(`转换颜色失败: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
isValidColor(color) {
|
||||
// 支持十六进制颜色格式
|
||||
const hexPattern = /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/;
|
||||
// 支持RGB格式
|
||||
const rgbPattern = /^rgb\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*\)$/;
|
||||
// 支持HSL格式
|
||||
const hslPattern = /^hsl\(\s*\d+\s*,\s*\d+%\s*,\s*\d+%\s*\)$/;
|
||||
|
||||
return hexPattern.test(color) || rgbPattern.test(color) || hslPattern.test(color);
|
||||
}
|
||||
|
||||
displayColorData(data) {
|
||||
// 显示主要颜色信息
|
||||
this.updateColorDisplay(data);
|
||||
|
||||
// 显示各种格式
|
||||
this.updateColorFormats(data);
|
||||
|
||||
// 显示颜色属性
|
||||
this.updateColorProperties(data);
|
||||
|
||||
// 显示配色方案
|
||||
this.updateColorPalette(data);
|
||||
|
||||
// 显示无障碍性信息
|
||||
this.updateAccessibilityInfo(data);
|
||||
}
|
||||
|
||||
updateColorDisplay(data) {
|
||||
const colorDisplay = document.getElementById('colorDisplay');
|
||||
const colorName = document.getElementById('colorName');
|
||||
const hexValue = document.getElementById('hexValue');
|
||||
|
||||
colorDisplay.style.backgroundColor = data.hex;
|
||||
colorName.textContent = data.name || '未知颜色';
|
||||
hexValue.textContent = data.hex;
|
||||
}
|
||||
|
||||
updateColorFormats(data) {
|
||||
// RGB
|
||||
if (data.rgb) {
|
||||
document.getElementById('rgbR').textContent = data.rgb.r;
|
||||
document.getElementById('rgbG').textContent = data.rgb.g;
|
||||
document.getElementById('rgbB').textContent = data.rgb.b;
|
||||
document.getElementById('rgbString').textContent = data.rgb.string;
|
||||
}
|
||||
|
||||
// HSL
|
||||
if (data.hsl) {
|
||||
document.getElementById('hslH').textContent = data.hsl.h + '°';
|
||||
document.getElementById('hslS').textContent = data.hsl.s + '%';
|
||||
document.getElementById('hslL').textContent = data.hsl.l + '%';
|
||||
document.getElementById('hslString').textContent = data.hsl.string;
|
||||
}
|
||||
|
||||
// HSV
|
||||
if (data.hsv) {
|
||||
document.getElementById('hsvH').textContent = data.hsv.h + '°';
|
||||
document.getElementById('hsvS').textContent = data.hsv.s + '%';
|
||||
document.getElementById('hsvV').textContent = data.hsv.v + '%';
|
||||
document.getElementById('hsvString').textContent = data.hsv.string;
|
||||
}
|
||||
|
||||
// CMYK
|
||||
if (data.cmyk) {
|
||||
document.getElementById('cmykC').textContent = data.cmyk.c + '%';
|
||||
document.getElementById('cmykM').textContent = data.cmyk.m + '%';
|
||||
document.getElementById('cmykY').textContent = data.cmyk.y + '%';
|
||||
document.getElementById('cmykK').textContent = data.cmyk.k + '%';
|
||||
document.getElementById('cmykString').textContent = data.cmyk.string;
|
||||
}
|
||||
|
||||
// LAB
|
||||
if (data.lab) {
|
||||
document.getElementById('labL').textContent = data.lab.l;
|
||||
document.getElementById('labA').textContent = data.lab.a;
|
||||
document.getElementById('labB').textContent = data.lab.b;
|
||||
document.getElementById('labString').textContent = data.lab.string;
|
||||
}
|
||||
}
|
||||
|
||||
updateColorProperties(data) {
|
||||
// 亮度
|
||||
if (data.brightness !== undefined) {
|
||||
document.getElementById('brightness').textContent = data.brightness.toFixed(2);
|
||||
}
|
||||
|
||||
// 对比度
|
||||
if (data.contrast) {
|
||||
document.getElementById('contrastWhite').textContent = data.contrast.white.toFixed(2);
|
||||
document.getElementById('contrastBlack').textContent = data.contrast.black.toFixed(2);
|
||||
}
|
||||
|
||||
// 最佳文字颜色
|
||||
if (data.accessibility && data.accessibility.best_text_color) {
|
||||
const bestTextColor = document.getElementById('bestTextColor');
|
||||
bestTextColor.textContent = data.accessibility.best_text_color;
|
||||
bestTextColor.style.color = data.accessibility.best_text_color;
|
||||
}
|
||||
}
|
||||
|
||||
updateColorPalette(data) {
|
||||
// 互补色
|
||||
if (data.complementary) {
|
||||
const complementary = document.getElementById('complementary');
|
||||
const complementaryHex = document.getElementById('complementaryHex');
|
||||
complementary.style.backgroundColor = data.complementary;
|
||||
complementaryHex.textContent = data.complementary;
|
||||
}
|
||||
|
||||
// 类似色
|
||||
if (data.analogous && data.analogous.length >= 2) {
|
||||
const analogous1 = document.getElementById('analogous1');
|
||||
const analogous2 = document.getElementById('analogous2');
|
||||
const analogous1Hex = document.getElementById('analogous1Hex');
|
||||
const analogous2Hex = document.getElementById('analogous2Hex');
|
||||
|
||||
analogous1.style.backgroundColor = data.analogous[0];
|
||||
analogous2.style.backgroundColor = data.analogous[1];
|
||||
analogous1Hex.textContent = data.analogous[0];
|
||||
analogous2Hex.textContent = data.analogous[1];
|
||||
}
|
||||
|
||||
// 三角色
|
||||
if (data.triadic && data.triadic.length >= 2) {
|
||||
const triadic1 = document.getElementById('triadic1');
|
||||
const triadic2 = document.getElementById('triadic2');
|
||||
const triadic1Hex = document.getElementById('triadic1Hex');
|
||||
const triadic2Hex = document.getElementById('triadic2Hex');
|
||||
|
||||
triadic1.style.backgroundColor = data.triadic[0];
|
||||
triadic2.style.backgroundColor = data.triadic[1];
|
||||
triadic1Hex.textContent = data.triadic[0];
|
||||
triadic2Hex.textContent = data.triadic[1];
|
||||
}
|
||||
}
|
||||
|
||||
updateAccessibilityInfo(data) {
|
||||
if (data.accessibility) {
|
||||
const aaNormal = document.getElementById('aaNormal');
|
||||
const aaLarge = document.getElementById('aaLarge');
|
||||
const aaaNormal = document.getElementById('aaaNormal');
|
||||
const aaaLarge = document.getElementById('aaaLarge');
|
||||
|
||||
this.updateAccessibilityStatus(aaNormal, data.accessibility.aa_normal);
|
||||
this.updateAccessibilityStatus(aaLarge, data.accessibility.aa_large);
|
||||
this.updateAccessibilityStatus(aaaNormal, data.accessibility.aaa_normal);
|
||||
this.updateAccessibilityStatus(aaaLarge, data.accessibility.aaa_large);
|
||||
}
|
||||
}
|
||||
|
||||
updateAccessibilityStatus(element, status) {
|
||||
element.textContent = status ? '通过' : '未通过';
|
||||
element.className = 'status ' + (status ? 'pass' : 'fail');
|
||||
}
|
||||
|
||||
// 复制颜色值到剪贴板
|
||||
copyToClipboard(text) {
|
||||
if (navigator.clipboard) {
|
||||
navigator.clipboard.writeText(text).then(() => {
|
||||
this.showToast('已复制到剪贴板');
|
||||
}).catch(err => {
|
||||
console.error('复制失败:', err);
|
||||
this.fallbackCopyTextToClipboard(text);
|
||||
});
|
||||
} else {
|
||||
this.fallbackCopyTextToClipboard(text);
|
||||
}
|
||||
}
|
||||
|
||||
fallbackCopyTextToClipboard(text) {
|
||||
const textArea = document.createElement('textarea');
|
||||
textArea.value = text;
|
||||
textArea.style.top = '0';
|
||||
textArea.style.left = '0';
|
||||
textArea.style.position = 'fixed';
|
||||
|
||||
document.body.appendChild(textArea);
|
||||
textArea.focus();
|
||||
textArea.select();
|
||||
|
||||
try {
|
||||
const successful = document.execCommand('copy');
|
||||
if (successful) {
|
||||
this.showToast('已复制到剪贴板');
|
||||
} else {
|
||||
this.showToast('复制失败');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('复制失败:', err);
|
||||
this.showToast('复制失败');
|
||||
}
|
||||
|
||||
document.body.removeChild(textArea);
|
||||
}
|
||||
|
||||
showToast(message) {
|
||||
// 创建简单的提示框
|
||||
const toast = document.createElement('div');
|
||||
toast.textContent = message;
|
||||
toast.style.cssText = `
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
background: #2d5a27;
|
||||
color: white;
|
||||
padding: 12px 20px;
|
||||
border-radius: 8px;
|
||||
z-index: 1000;
|
||||
font-size: 14px;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
||||
animation: slideIn 0.3s ease;
|
||||
`;
|
||||
|
||||
// 添加动画样式
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
@keyframes slideIn {
|
||||
from { transform: translateX(100%); opacity: 0; }
|
||||
to { transform: translateX(0); opacity: 1; }
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
|
||||
document.body.appendChild(toast);
|
||||
|
||||
setTimeout(() => {
|
||||
toast.style.animation = 'slideIn 0.3s ease reverse';
|
||||
setTimeout(() => {
|
||||
document.body.removeChild(toast);
|
||||
document.head.removeChild(style);
|
||||
}, 300);
|
||||
}, 2000);
|
||||
}
|
||||
}
|
||||
|
||||
// 添加点击复制功能
|
||||
function addCopyListeners() {
|
||||
const colorTool = window.colorTool;
|
||||
|
||||
// 为所有颜色值添加点击复制功能
|
||||
document.addEventListener('click', (e) => {
|
||||
const target = e.target;
|
||||
|
||||
// 检查是否点击了颜色值相关元素
|
||||
if (target.id === 'hexValue' ||
|
||||
target.id === 'rgbString' ||
|
||||
target.id === 'hslString' ||
|
||||
target.id === 'hsvString' ||
|
||||
target.id === 'cmykString' ||
|
||||
target.id === 'labString' ||
|
||||
target.id === 'complementaryHex' ||
|
||||
target.id === 'analogous1Hex' ||
|
||||
target.id === 'analogous2Hex' ||
|
||||
target.id === 'triadic1Hex' ||
|
||||
target.id === 'triadic2Hex') {
|
||||
|
||||
const text = target.textContent;
|
||||
if (text && colorTool) {
|
||||
colorTool.copyToClipboard(text);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 页面加载完成后初始化
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
window.colorTool = new ColorTool();
|
||||
addCopyListeners();
|
||||
|
||||
// 添加复制提示
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
#hexValue, #rgbString, #hslString, #hsvString, #cmykString, #labString,
|
||||
#complementaryHex, #analogous1Hex, #analogous2Hex, #triadic1Hex, #triadic2Hex {
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
#hexValue:hover, #rgbString:hover, #hslString:hover, #hsvString:hover,
|
||||
#cmykString:hover, #labString:hover, #complementaryHex:hover,
|
||||
#analogous1Hex:hover, #analogous2Hex:hover, #triadic1Hex:hover, #triadic2Hex:hover {
|
||||
background: rgba(45, 90, 39, 0.1);
|
||||
border-radius: 4px;
|
||||
padding: 2px 4px;
|
||||
margin: -2px -4px;
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
});
|
||||
@@ -1,637 +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: #2d3748;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* 头部样式 */
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 2rem;
|
||||
color: #2d5a27;
|
||||
margin-bottom: 10px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
color: #4a5568;
|
||||
font-size: 1rem;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
/* 主要内容区域 */
|
||||
.main-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 30px;
|
||||
}
|
||||
|
||||
/* 输入区域 */
|
||||
.input-section {
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
padding: 25px;
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 4px 20px rgba(45, 90, 39, 0.1);
|
||||
border: 1px solid rgba(144, 205, 144, 0.3);
|
||||
}
|
||||
|
||||
.input-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.input-group label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
font-weight: 500;
|
||||
color: #2d5a27;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.input-group input,
|
||||
.input-group select {
|
||||
width: 100%;
|
||||
padding: 12px 15px;
|
||||
border: 2px solid #90cd90;
|
||||
border-radius: 10px;
|
||||
font-size: 1rem;
|
||||
transition: all 0.3s ease;
|
||||
background: #f8fff8;
|
||||
}
|
||||
|
||||
.input-group input:focus,
|
||||
.input-group select:focus {
|
||||
outline: none;
|
||||
border-color: #2d5a27;
|
||||
box-shadow: 0 0 0 3px rgba(45, 90, 39, 0.1);
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.button-group {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
margin-top: 25px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
flex: 1;
|
||||
padding: 15px 20px;
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: linear-gradient(135deg, #2d5a27, #4a7c59);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: linear-gradient(135deg, #1e3a1a, #2d5a27);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 20px rgba(45, 90, 39, 0.3);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: linear-gradient(135deg, #90cd90, #a8d8a8);
|
||||
color: #2d5a27;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: linear-gradient(135deg, #7bb87b, #90cd90);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 20px rgba(144, 205, 144, 0.4);
|
||||
}
|
||||
|
||||
/* 结果展示区域 */
|
||||
.result-section {
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
padding: 25px;
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 4px 20px rgba(45, 90, 39, 0.1);
|
||||
border: 1px solid rgba(144, 205, 144, 0.3);
|
||||
}
|
||||
|
||||
/* 颜色预览 */
|
||||
.color-preview {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
margin-bottom: 30px;
|
||||
padding: 20px;
|
||||
background: #f8fff8;
|
||||
border-radius: 12px;
|
||||
border: 1px solid rgba(144, 205, 144, 0.2);
|
||||
}
|
||||
|
||||
.color-box {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 12px;
|
||||
border: 3px solid #ffffff;
|
||||
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.color-info h3 {
|
||||
color: #2d5a27;
|
||||
margin-bottom: 5px;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.color-info p {
|
||||
color: #4a5568;
|
||||
font-size: 1.1rem;
|
||||
font-weight: 500;
|
||||
font-family: 'Courier New', monospace;
|
||||
}
|
||||
|
||||
/* 颜色格式展示 */
|
||||
.color-formats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 20px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.format-group {
|
||||
background: #f8fff8;
|
||||
padding: 15px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid rgba(144, 205, 144, 0.2);
|
||||
}
|
||||
|
||||
.format-group h4 {
|
||||
color: #2d5a27;
|
||||
margin-bottom: 10px;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.format-values {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.format-values span {
|
||||
background: #90cd90;
|
||||
color: #2d5a27;
|
||||
padding: 4px 8px;
|
||||
border-radius: 6px;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.format-group p {
|
||||
font-family: 'Courier New', monospace;
|
||||
color: #4a5568;
|
||||
font-size: 0.9rem;
|
||||
background: #ffffff;
|
||||
padding: 8px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid rgba(144, 205, 144, 0.2);
|
||||
}
|
||||
|
||||
/* 颜色属性 */
|
||||
.color-properties {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 15px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.property-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background: #f8fff8;
|
||||
padding: 12px 15px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid rgba(144, 205, 144, 0.2);
|
||||
}
|
||||
|
||||
.property-item label {
|
||||
color: #2d5a27;
|
||||
font-weight: 500;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.property-item span {
|
||||
color: #4a5568;
|
||||
font-weight: 600;
|
||||
font-family: 'Courier New', monospace;
|
||||
}
|
||||
|
||||
/* 配色方案 */
|
||||
.color-palette {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.color-palette h4 {
|
||||
color: #2d5a27;
|
||||
margin-bottom: 15px;
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.palette-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.palette-item {
|
||||
background: #f8fff8;
|
||||
padding: 15px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid rgba(144, 205, 144, 0.2);
|
||||
}
|
||||
|
||||
.palette-item label {
|
||||
display: block;
|
||||
color: #2d5a27;
|
||||
font-weight: 500;
|
||||
margin-bottom: 10px;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.color-sample {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 8px;
|
||||
border: 2px solid #ffffff;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
display: inline-block;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.analogous-colors,
|
||||
.triadic-colors {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.analogous-hex,
|
||||
.triadic-hex {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 0.85rem;
|
||||
color: #4a5568;
|
||||
}
|
||||
|
||||
/* 无障碍性信息 */
|
||||
.accessibility-info h4 {
|
||||
color: #2d5a27;
|
||||
margin-bottom: 15px;
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.accessibility-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.accessibility-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background: #f8fff8;
|
||||
padding: 10px 15px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid rgba(144, 205, 144, 0.2);
|
||||
}
|
||||
|
||||
.accessibility-item span:first-child {
|
||||
color: #2d5a27;
|
||||
font-weight: 500;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.status {
|
||||
padding: 4px 8px;
|
||||
border-radius: 6px;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.status.pass {
|
||||
background: #90cd90;
|
||||
color: #2d5a27;
|
||||
}
|
||||
|
||||
.status.fail {
|
||||
background: #ffcccb;
|
||||
color: #d32f2f;
|
||||
}
|
||||
|
||||
/* 加载和错误状态 */
|
||||
.loading,
|
||||
.error {
|
||||
text-align: center;
|
||||
padding: 40px 20px;
|
||||
border-radius: 12px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.loading {
|
||||
background: rgba(144, 205, 144, 0.1);
|
||||
border: 1px solid rgba(144, 205, 144, 0.3);
|
||||
}
|
||||
|
||||
.error {
|
||||
background: rgba(255, 204, 203, 0.3);
|
||||
border: 1px solid rgba(211, 47, 47, 0.3);
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 4px solid rgba(144, 205, 144, 0.3);
|
||||
border-top: 4px solid #2d5a27;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin: 0 auto 15px;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.loading p {
|
||||
color: #2d5a27;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.error p {
|
||||
color: #d32f2f;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 平板端适配 (768px - 1024px) */
|
||||
@media (min-width: 768px) and (max-width: 1024px) {
|
||||
.container {
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
gap: 35px;
|
||||
}
|
||||
|
||||
.input-section,
|
||||
.result-section {
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
.color-preview {
|
||||
gap: 25px;
|
||||
}
|
||||
|
||||
.color-box {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.color-formats {
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
}
|
||||
|
||||
.palette-group {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 电脑端适配 (1025px+) */
|
||||
@media (min-width: 1025px) {
|
||||
.container {
|
||||
padding: 40px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 3rem;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
gap: 40px;
|
||||
}
|
||||
|
||||
.input-section,
|
||||
.result-section {
|
||||
padding: 35px;
|
||||
}
|
||||
|
||||
.color-preview {
|
||||
gap: 30px;
|
||||
padding: 25px;
|
||||
}
|
||||
|
||||
.color-box {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
}
|
||||
|
||||
.color-info h3 {
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
|
||||
.color-info p {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.color-formats {
|
||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||||
gap: 25px;
|
||||
}
|
||||
|
||||
.format-group {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.palette-group {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 25px;
|
||||
}
|
||||
|
||||
.button-group {
|
||||
max-width: 500px;
|
||||
margin: 25px auto 0;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 18px 25px;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* 手机端优化 (最高优先级) */
|
||||
@media (max-width: 767px) {
|
||||
.container {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.header {
|
||||
margin-bottom: 25px;
|
||||
padding: 15px 0;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
gap: 25px;
|
||||
}
|
||||
|
||||
.input-section,
|
||||
.result-section {
|
||||
padding: 20px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.input-group input,
|
||||
.input-group select {
|
||||
padding: 14px 12px;
|
||||
font-size: 16px; /* 防止iOS缩放 */
|
||||
}
|
||||
|
||||
.button-group {
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 16px 20px;
|
||||
font-size: 1rem;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.color-preview {
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
gap: 15px;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.color-box {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.color-formats {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.format-group {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.format-values {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.color-properties {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.property-item {
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
text-align: center;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.palette-group {
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.palette-item {
|
||||
padding: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.analogous-colors,
|
||||
.triadic-colors {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.analogous-hex,
|
||||
.triadic-hex {
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.accessibility-grid {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.accessibility-item {
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
text-align: center;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.loading,
|
||||
.error {
|
||||
padding: 30px 15px;
|
||||
margin: 15px 0;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 35px;
|
||||
height: 35px;
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
{
|
||||
"code": 200,
|
||||
"message": "获取成功。数据来自官方/权威源头,以确保稳定与实时。开源地址 https://github.com/vikiboss/60s,反馈群 595941841",
|
||||
"data": {
|
||||
"hex": "#A59619",
|
||||
"name": "红色系",
|
||||
"rgb": {
|
||||
"r": 165,
|
||||
"g": 150,
|
||||
"b": 25,
|
||||
"string": "rgb(165, 150, 25)"
|
||||
},
|
||||
"hsl": {
|
||||
"h": 54,
|
||||
"s": 74,
|
||||
"l": 37,
|
||||
"string": "hsl(54, 74%, 37%)"
|
||||
},
|
||||
"hsv": {
|
||||
"h": 54,
|
||||
"s": 85,
|
||||
"v": 65,
|
||||
"string": "hsv(54, 85%, 65%)"
|
||||
},
|
||||
"cmyk": {
|
||||
"c": 0,
|
||||
"m": 9,
|
||||
"y": 85,
|
||||
"k": 35,
|
||||
"string": "cmyk(0%, 9%, 85%, 35%)"
|
||||
},
|
||||
"lab": {
|
||||
"l": 62,
|
||||
"a": -7,
|
||||
"b": 61,
|
||||
"string": "lab(62, -7, 61)"
|
||||
},
|
||||
"brightness": 140.235,
|
||||
"contrast": {
|
||||
"white": 3.01,
|
||||
"black": 6.98
|
||||
},
|
||||
"accessibility": {
|
||||
"aa_normal": true,
|
||||
"aa_large": true,
|
||||
"aaa_normal": false,
|
||||
"aaa_large": true,
|
||||
"best_text_color": "#000000"
|
||||
},
|
||||
"complementary": "#1926A4",
|
||||
"analogous": [
|
||||
"#A45019",
|
||||
"#6CA419"
|
||||
],
|
||||
"triadic": [
|
||||
"#19A496",
|
||||
"#9619A4"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
/* 彩虹背景相关样式 */
|
||||
body {
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
#e8f5e8 0%,
|
||||
#f1f8e9 25%,
|
||||
#dcedc8 50%,
|
||||
#c8e6c8 75%,
|
||||
#e8f5e8 100%
|
||||
);
|
||||
background-size: 200% 200%;
|
||||
animation: gentleGradient 20s ease infinite;
|
||||
background-attachment: fixed;
|
||||
min-height: 100vh;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@keyframes gentleGradient {
|
||||
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:
|
||||
radial-gradient(circle at 20% 20%, rgba(129, 199, 132, 0.08) 0%, transparent 50%),
|
||||
radial-gradient(circle at 80% 80%, rgba(165, 214, 167, 0.06) 0%, transparent 50%),
|
||||
radial-gradient(circle at 40% 80%, rgba(200, 230, 201, 0.05) 0%, transparent 40%),
|
||||
radial-gradient(circle at 60% 20%, rgba(220, 237, 200, 0.04) 0%, transparent 40%);
|
||||
pointer-events: none;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
/* 淡雅绿色点缀 */
|
||||
body::after {
|
||||
content: '';
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image:
|
||||
radial-gradient(circle at 15% 15%, rgba(129, 199, 132, 0.3) 1px, transparent 1px),
|
||||
radial-gradient(circle at 45% 25%, rgba(165, 214, 167, 0.25) 1px, transparent 1px),
|
||||
radial-gradient(circle at 75% 35%, rgba(200, 230, 201, 0.2) 1px, transparent 1px),
|
||||
radial-gradient(circle at 25% 65%, rgba(220, 237, 200, 0.15) 1px, transparent 1px),
|
||||
radial-gradient(circle at 85% 75%, rgba(129, 199, 132, 0.2) 1px, transparent 1px);
|
||||
background-size: 300px 300px, 400px 400px, 350px 350px, 450px 450px, 380px 380px;
|
||||
pointer-events: none;
|
||||
z-index: -1;
|
||||
opacity: 0.4;
|
||||
}
|
||||
@@ -1,860 +0,0 @@
|
||||
/* 背景样式 */
|
||||
.background-container {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: -1;
|
||||
overflow: hidden;
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.modern-gradient {
|
||||
position: absolute;
|
||||
top: -50%;
|
||||
left: -50%;
|
||||
width: 200%;
|
||||
height: 200%;
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
rgba(129, 199, 132, 0.4) 0%,
|
||||
rgba(165, 214, 167, 0.3) 25%,
|
||||
rgba(200, 230, 201, 0.2) 50%,
|
||||
rgba(220, 237, 200, 0.3) 75%,
|
||||
rgba(232, 245, 233, 0.4) 100%
|
||||
);
|
||||
animation: gradient-flow 20s ease-in-out infinite;
|
||||
border-radius: 30% 70% 70% 30% / 30% 30% 70% 70%;
|
||||
}
|
||||
|
||||
.modern-gradient::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: radial-gradient(
|
||||
circle at 30% 70%,
|
||||
rgba(129, 199, 132, 0.5) 0%,
|
||||
transparent 50%
|
||||
), radial-gradient(
|
||||
circle at 70% 30%,
|
||||
rgba(165, 214, 167, 0.4) 0%,
|
||||
transparent 50%
|
||||
);
|
||||
animation: pulse-effect 15s ease-in-out infinite alternate;
|
||||
border-radius: 30% 70% 70% 30% / 30% 30% 70% 70%;
|
||||
}
|
||||
|
||||
@keyframes gradient-flow {
|
||||
0%, 100% {
|
||||
transform: rotate(0deg) scale(1);
|
||||
opacity: 0.8;
|
||||
}
|
||||
25% {
|
||||
transform: rotate(90deg) scale(1.1);
|
||||
opacity: 0.6;
|
||||
}
|
||||
50% {
|
||||
transform: rotate(180deg) scale(0.9);
|
||||
opacity: 0.9;
|
||||
}
|
||||
75% {
|
||||
transform: rotate(270deg) scale(1.05);
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pulse-effect {
|
||||
0% {
|
||||
transform: scale(1) rotate(0deg);
|
||||
opacity: 0.5;
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.2) rotate(180deg);
|
||||
opacity: 0.8;
|
||||
}
|
||||
100% {
|
||||
transform: scale(1) rotate(360deg);
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
/* 主样式 */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'PingFang SC', 'Microsoft YaHei', sans-serif;
|
||||
color: #333;
|
||||
background-color: #f8f9fa;
|
||||
position: relative;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 24px;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
background-color: rgba(255, 255, 255, 0.85);
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.08);
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
header, .header {
|
||||
text-align: center;
|
||||
margin-bottom: 28px;
|
||||
padding-bottom: 20px;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.header-icon {
|
||||
font-size: 3rem;
|
||||
margin-bottom: 10px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
header h1, .title {
|
||||
background: linear-gradient(135deg, #66bb6a, #81c784);
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
color: transparent;
|
||||
margin-bottom: 14px;
|
||||
font-size: 2.4rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.5px;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
color: #666;
|
||||
font-size: 1.1rem;
|
||||
margin-bottom: 20px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.tab-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.tab-btn {
|
||||
background: linear-gradient(135deg, #f0f0f0, #e8e8e8);
|
||||
border: none;
|
||||
padding: 12px 20px;
|
||||
border-radius: 25px;
|
||||
cursor: pointer;
|
||||
font-size: 0.95rem;
|
||||
font-weight: 500;
|
||||
color: #666;
|
||||
transition: all 0.3s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.tab-btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.tab-btn.active {
|
||||
background: linear-gradient(135deg, #66bb6a, #81c784);
|
||||
color: white;
|
||||
box-shadow: 0 4px 16px rgba(102, 187, 106, 0.3);
|
||||
}
|
||||
|
||||
.tab-icon {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.refresh-btn {
|
||||
background: linear-gradient(135deg, #81c784, #a5d6a7);
|
||||
border: none;
|
||||
padding: 12px 24px;
|
||||
border-radius: 25px;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
font-size: 0.95rem;
|
||||
font-weight: 500;
|
||||
margin-top: 15px;
|
||||
transition: all 0.3s ease;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
box-shadow: 0 4px 12px rgba(129, 199, 132, 0.3);
|
||||
}
|
||||
|
||||
.refresh-btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 16px rgba(129, 199, 132, 0.4);
|
||||
}
|
||||
|
||||
.btn-icon {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.time-icon {
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
.update-time {
|
||||
color: #666;
|
||||
font-size: 0.9rem;
|
||||
background-color: rgba(0, 0, 0, 0.03);
|
||||
padding: 8px 16px;
|
||||
border-radius: 24px;
|
||||
display: inline-block;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.hot-list {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.hot-item {
|
||||
padding: 20px;
|
||||
margin-bottom: 16px;
|
||||
border-radius: 12px;
|
||||
background-color: white;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.04);
|
||||
transition: all 0.3s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border: 1px solid rgba(0, 0, 0, 0.03);
|
||||
}
|
||||
|
||||
.hot-item:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08);
|
||||
border-color: rgba(129, 199, 132, 0.3);
|
||||
}
|
||||
|
||||
.hot-rank {
|
||||
font-size: 1.2rem;
|
||||
font-weight: bold;
|
||||
color: #66bb6a;
|
||||
margin-right: 18px;
|
||||
min-width: 38px;
|
||||
text-align: center;
|
||||
background-color: rgba(129, 199, 132, 0.1);
|
||||
border-radius: 50%;
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.hot-rank.top-1 {
|
||||
background: linear-gradient(135deg, #66bb6a, #81c784);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.hot-rank.top-2 {
|
||||
background: linear-gradient(135deg, #81c784, #a5d6a7);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.hot-rank.top-3 {
|
||||
background: linear-gradient(135deg, #a5d6a7, #c8e6c9);
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.hot-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.hot-title {
|
||||
font-size: 1.15rem;
|
||||
margin-bottom: 8px;
|
||||
color: #333;
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
line-height: 1.5;
|
||||
font-weight: 500;
|
||||
transition: color 0.2s ease;
|
||||
}
|
||||
|
||||
.hot-title:hover {
|
||||
color: #66bb6a;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.loading {
|
||||
text-align: center;
|
||||
padding: 40px;
|
||||
color: #666;
|
||||
font-size: 1.1rem;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.loading-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.rainbow-spinner {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border: 4px solid rgba(129, 199, 132, 0.2);
|
||||
border-top: 4px solid #81c784;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.loading-emoji {
|
||||
font-size: 2rem;
|
||||
animation: bounce 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes bounce {
|
||||
0%, 20%, 50%, 80%, 100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
40% {
|
||||
transform: translateY(-10px);
|
||||
}
|
||||
60% {
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
}
|
||||
|
||||
.loading-dots {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.loading-dots span {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background: #81c784;
|
||||
border-radius: 50%;
|
||||
animation: loadingDots 1.4s ease-in-out infinite both;
|
||||
}
|
||||
|
||||
.loading-dots span:nth-child(1) { animation-delay: -0.32s; }
|
||||
.loading-dots span:nth-child(2) { animation-delay: -0.16s; }
|
||||
.loading-dots span:nth-child(3) { animation-delay: 0s; }
|
||||
|
||||
@keyframes loadingDots {
|
||||
0%, 80%, 100% {
|
||||
transform: scale(0);
|
||||
}
|
||||
40% {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.news-list {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
/* 新闻项目卡片 - 移动端优先设计 */
|
||||
.news-item {
|
||||
background: white;
|
||||
border-radius: 16px;
|
||||
padding: 16px;
|
||||
margin-bottom: 12px;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06);
|
||||
transition: all 0.3s ease;
|
||||
border: 1px solid rgba(0, 0, 0, 0.05);
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.news-item:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.1);
|
||||
border-color: rgba(129, 199, 132, 0.3);
|
||||
}
|
||||
|
||||
/* 排名容器 */
|
||||
.news-rank-container {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
padding-top: 2px;
|
||||
}
|
||||
|
||||
.news-rank {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 12px;
|
||||
background: linear-gradient(135deg, #f0f0f0, #e8e8e8);
|
||||
color: #666;
|
||||
font-weight: 600;
|
||||
position: relative;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.news-rank.rank-1 {
|
||||
background: linear-gradient(135deg, #66bb6a, #81c784);
|
||||
color: white;
|
||||
box-shadow: 0 4px 12px rgba(102, 187, 106, 0.3);
|
||||
}
|
||||
|
||||
.news-rank.rank-2 {
|
||||
background: linear-gradient(135deg, #81c784, #a5d6a7);
|
||||
color: white;
|
||||
box-shadow: 0 4px 12px rgba(129, 199, 132, 0.3);
|
||||
}
|
||||
|
||||
.news-rank.rank-3 {
|
||||
background: linear-gradient(135deg, #a5d6a7, #c8e6c9);
|
||||
color: #333;
|
||||
box-shadow: 0 4px 12px rgba(165, 214, 167, 0.3);
|
||||
}
|
||||
|
||||
.rank-number {
|
||||
font-size: 0.9rem;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.rank-emoji {
|
||||
font-size: 0.7rem;
|
||||
line-height: 1;
|
||||
margin-top: 1px;
|
||||
}
|
||||
|
||||
/* 内容包装器 */
|
||||
.news-content-wrapper {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
/* 标题 */
|
||||
.news-title {
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
line-height: 1.4;
|
||||
margin: 0;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
cursor: pointer;
|
||||
transition: color 0.2s ease;
|
||||
}
|
||||
|
||||
.news-title:hover {
|
||||
color: #66bb6a;
|
||||
}
|
||||
|
||||
/* 元信息行 */
|
||||
.news-meta-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.news-author, .news-time {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
color: #666;
|
||||
font-size: 0.8rem;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.meta-icon {
|
||||
font-size: 0.9rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.meta-text {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 统计信息行 */
|
||||
.news-stats-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.news-score {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
background: linear-gradient(135deg, #81c784, #a5d6a7);
|
||||
color: white;
|
||||
padding: 4px 10px;
|
||||
border-radius: 12px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.heat-level {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.score-text {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.news-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
background: linear-gradient(135deg, #66bb6a, #81c784);
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
padding: 6px 12px;
|
||||
border-radius: 12px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 2px 6px rgba(102, 187, 106, 0.3);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.news-link:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 10px rgba(102, 187, 106, 0.4);
|
||||
text-decoration: none;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.link-icon {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.link-text {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
text-align: center;
|
||||
padding: 40px;
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.error-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.error-icon {
|
||||
font-size: 3rem;
|
||||
}
|
||||
|
||||
.error-content h3 {
|
||||
color: #66bb6a;
|
||||
margin: 0;
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
|
||||
.error-content p {
|
||||
color: #666;
|
||||
margin: 0;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.retry-btn {
|
||||
background: linear-gradient(135deg, #66bb6a, #81c784);
|
||||
border: none;
|
||||
padding: 12px 24px;
|
||||
border-radius: 25px;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
font-size: 0.95rem;
|
||||
font-weight: 500;
|
||||
transition: all 0.3s ease;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
box-shadow: 0 4px 12px rgba(102, 187, 106, 0.3);
|
||||
}
|
||||
|
||||
.retry-btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 16px rgba(102, 187, 106, 0.4);
|
||||
}
|
||||
|
||||
footer {
|
||||
text-align: center;
|
||||
margin-top: 30px;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.06);
|
||||
color: #666;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
/* 响应式设计 - 移动端优化 */
|
||||
@media (max-width: 1024px) and (min-width: 768px) {
|
||||
.container {
|
||||
max-width: 90%;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
header h1 {
|
||||
font-size: 2.2rem;
|
||||
}
|
||||
|
||||
.hot-item {
|
||||
padding: 18px;
|
||||
}
|
||||
|
||||
.hot-title {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
body {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 95%;
|
||||
margin: 12px auto;
|
||||
padding: 8px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
header, .header {
|
||||
margin-bottom: 20px;
|
||||
padding: 12px 0 16px 0;
|
||||
}
|
||||
|
||||
header h1, .title {
|
||||
font-size: 1.6rem;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.tab-container {
|
||||
gap: 8px;
|
||||
margin: 16px 0;
|
||||
}
|
||||
|
||||
.tab-btn {
|
||||
padding: 8px 12px;
|
||||
font-size: 0.8rem;
|
||||
min-width: auto;
|
||||
}
|
||||
|
||||
.refresh-btn {
|
||||
padding: 8px 10px;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.update-time {
|
||||
font-size: 0.85rem;
|
||||
padding: 6px 12px;
|
||||
}
|
||||
|
||||
.hot-item {
|
||||
padding: 16px;
|
||||
margin-bottom: 12px;
|
||||
border-radius: 10px;
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.hot-rank {
|
||||
font-size: 1.1rem;
|
||||
margin-right: 14px;
|
||||
min-width: 32px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.hot-title {
|
||||
font-size: 1rem;
|
||||
line-height: 1.5;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.news-item {
|
||||
padding: 12px;
|
||||
margin-bottom: 10px;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.news-rank {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.rank-number {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.rank-emoji {
|
||||
font-size: 0.6rem;
|
||||
}
|
||||
|
||||
.news-title {
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.news-meta-row {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.news-author, .news-time {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.news-score {
|
||||
padding: 3px 8px;
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
.news-link {
|
||||
padding: 5px 10px;
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
.link-text {
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
footer {
|
||||
margin-top: 24px;
|
||||
padding-top: 16px;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.container {
|
||||
margin: 8px auto;
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
header h1, .title {
|
||||
font-size: 1.6rem;
|
||||
}
|
||||
|
||||
.hot-item {
|
||||
padding: 14px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.hot-rank {
|
||||
font-size: 1rem;
|
||||
margin-right: 12px;
|
||||
min-width: 30px;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.hot-title {
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.news-item {
|
||||
padding: 10px;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.news-rank {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
}
|
||||
|
||||
.rank-number {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.news-title {
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.news-meta-row {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.news-stats-row {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.news-author, .news-time {
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
.news-score {
|
||||
font-size: 0.65rem;
|
||||
}
|
||||
|
||||
.news-link {
|
||||
font-size: 0.65rem;
|
||||
padding: 4px 8px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 减少动画以节省电池 */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.modern-gradient,
|
||||
.modern-gradient::before {
|
||||
animation: none;
|
||||
}
|
||||
|
||||
.modern-gradient {
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
rgba(129, 199, 132, 0.3) 0%,
|
||||
rgba(200, 230, 201, 0.2) 50%,
|
||||
rgba(232, 245, 233, 0.25) 100%
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,203 +0,0 @@
|
||||
.background-container {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: -1;
|
||||
overflow: hidden;
|
||||
background-color: #f0f7ff;
|
||||
}
|
||||
|
||||
/* 太阳元素 */
|
||||
.sun {
|
||||
position: absolute;
|
||||
top: 50px;
|
||||
right: 35%;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
background: radial-gradient(circle, #ffeb3b 30%, #ff9800 70%);
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 0 40px rgba(255, 152, 0, 0.6);
|
||||
z-index: 0;
|
||||
animation: sun-pulse 8s ease-in-out infinite;
|
||||
}
|
||||
|
||||
/* 蓝色云元素 */
|
||||
.cloud {
|
||||
position: absolute;
|
||||
background: rgba(135, 206, 250, 0.8);
|
||||
border-radius: 50px;
|
||||
box-shadow: 0 8px 15px rgba(0, 0, 0, 0.1);
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.cloud-1 {
|
||||
top: 120px;
|
||||
left: -150px;
|
||||
width: 120px;
|
||||
height: 40px;
|
||||
animation: cloud-float 15s linear infinite;
|
||||
}
|
||||
|
||||
.cloud-2 {
|
||||
top: 180px;
|
||||
right: -150px;
|
||||
width: 160px;
|
||||
height: 50px;
|
||||
animation: cloud-float 20s linear infinite reverse;
|
||||
}
|
||||
|
||||
.cloud-3 {
|
||||
top: 60px;
|
||||
left: -100px;
|
||||
width: 100px;
|
||||
height: 35px;
|
||||
animation: cloud-float 12s linear infinite;
|
||||
}
|
||||
|
||||
/* 云朵的伪元素,创建更自然的形状 */
|
||||
.cloud::before,
|
||||
.cloud::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
background: rgba(135, 206, 250, 0.8);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.cloud::before {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
top: -20px;
|
||||
left: 15px;
|
||||
}
|
||||
|
||||
.cloud::after {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
top: -30px;
|
||||
right: 15px;
|
||||
}
|
||||
|
||||
@keyframes sun-pulse {
|
||||
0%, 100% {
|
||||
transform: scale(1);
|
||||
box-shadow: 0 0 40px rgba(255, 152, 0, 0.6);
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.1);
|
||||
box-shadow: 0 0 60px rgba(255, 152, 0, 0.8);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes cloud-float {
|
||||
0% {
|
||||
transform: translateX(-150px);
|
||||
}
|
||||
100% {
|
||||
transform: translateX(calc(100vw + 150px));
|
||||
}
|
||||
}
|
||||
|
||||
.modern-gradient {
|
||||
position: absolute;
|
||||
top: -50%;
|
||||
left: -50%;
|
||||
width: 200%;
|
||||
height: 200%;
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
rgba(24, 144, 255, 0.6) 0%,
|
||||
rgba(64, 169, 255, 0.5) 20%,
|
||||
rgba(135, 208, 255, 0.4) 40%,
|
||||
rgba(255, 175, 64, 0.4) 60%,
|
||||
rgba(255, 122, 69, 0.5) 80%,
|
||||
rgba(245, 85, 65, 0.6) 100%
|
||||
);
|
||||
animation: gradient-flow 25s ease-in-out infinite;
|
||||
border-radius: 40% 60% 60% 40% / 40% 40% 60% 60%;
|
||||
filter: blur(30px);
|
||||
}
|
||||
|
||||
.modern-gradient::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: radial-gradient(
|
||||
circle at 25% 65%,
|
||||
rgba(24, 144, 255, 0.6) 0%,
|
||||
transparent 60%
|
||||
), radial-gradient(
|
||||
circle at 75% 35%,
|
||||
rgba(245, 85, 65, 0.5) 0%,
|
||||
transparent 60%
|
||||
);
|
||||
animation: pulse-effect 18s ease-in-out infinite alternate;
|
||||
border-radius: 40% 60% 60% 40% / 40% 40% 60% 60%;
|
||||
filter: blur(20px);
|
||||
}
|
||||
|
||||
@keyframes gradient-flow {
|
||||
0%, 100% {
|
||||
transform: rotate(0deg) scale(1);
|
||||
opacity: 0.8;
|
||||
}
|
||||
25% {
|
||||
transform: rotate(90deg) scale(1.1);
|
||||
opacity: 0.6;
|
||||
}
|
||||
50% {
|
||||
transform: rotate(180deg) scale(0.9);
|
||||
opacity: 0.9;
|
||||
}
|
||||
75% {
|
||||
transform: rotate(270deg) scale(1.05);
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pulse-effect {
|
||||
0% {
|
||||
transform: scale(1) rotate(0deg);
|
||||
opacity: 0.5;
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.2) rotate(180deg);
|
||||
opacity: 0.8;
|
||||
}
|
||||
100% {
|
||||
transform: scale(1) rotate(360deg);
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
/* 手机端背景优化 */
|
||||
@media (max-width: 768px) {
|
||||
.modern-gradient {
|
||||
animation-duration: 25s;
|
||||
}
|
||||
|
||||
.modern-gradient::before {
|
||||
animation-duration: 18s;
|
||||
}
|
||||
}
|
||||
|
||||
/* 减少动画以节省电池 */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.modern-gradient,
|
||||
.modern-gradient::before {
|
||||
animation: none;
|
||||
}
|
||||
|
||||
.modern-gradient {
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
rgba(64, 169, 255, 0.3) 0%,
|
||||
rgba(255, 175, 64, 0.2) 50%,
|
||||
rgba(255, 122, 69, 0.25) 100%
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,352 +0,0 @@
|
||||
/* 背景样式 */
|
||||
.background-container {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: -1;
|
||||
overflow: hidden;
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.modern-gradient {
|
||||
position: absolute;
|
||||
top: -50%;
|
||||
left: -50%;
|
||||
width: 200%;
|
||||
height: 200%;
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
rgba(64, 169, 255, 0.4) 0%,
|
||||
rgba(120, 192, 255, 0.3) 25%,
|
||||
rgba(255, 175, 64, 0.2) 50%,
|
||||
rgba(255, 140, 50, 0.3) 75%,
|
||||
rgba(255, 122, 69, 0.4) 100%
|
||||
);
|
||||
animation: gradient-flow 20s ease-in-out infinite;
|
||||
border-radius: 30% 70% 70% 30% / 30% 30% 70% 70%;
|
||||
}
|
||||
|
||||
.modern-gradient::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: radial-gradient(
|
||||
circle at 30% 70%,
|
||||
rgba(64, 169, 255, 0.5) 0%,
|
||||
transparent 50%
|
||||
), radial-gradient(
|
||||
circle at 70% 30%,
|
||||
rgba(255, 140, 50, 0.4) 0%,
|
||||
transparent 50%
|
||||
);
|
||||
animation: pulse-effect 15s ease-in-out infinite alternate;
|
||||
border-radius: 30% 70% 70% 30% / 30% 30% 70% 70%;
|
||||
}
|
||||
|
||||
@keyframes gradient-flow {
|
||||
0%, 100% {
|
||||
transform: rotate(0deg) scale(1);
|
||||
opacity: 0.8;
|
||||
}
|
||||
25% {
|
||||
transform: rotate(90deg) scale(1.1);
|
||||
opacity: 0.6;
|
||||
}
|
||||
50% {
|
||||
transform: rotate(180deg) scale(0.9);
|
||||
opacity: 0.9;
|
||||
}
|
||||
75% {
|
||||
transform: rotate(270deg) scale(1.05);
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pulse-effect {
|
||||
0% {
|
||||
transform: scale(1) rotate(0deg);
|
||||
opacity: 0.5;
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.2) rotate(180deg);
|
||||
opacity: 0.8;
|
||||
}
|
||||
100% {
|
||||
transform: scale(1) rotate(360deg);
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
/* 主样式 */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'PingFang SC', 'Microsoft YaHei', sans-serif;
|
||||
color: #333;
|
||||
background-color: #f0f7ff;
|
||||
position: relative;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 800px;
|
||||
margin: 20px auto;
|
||||
padding: 28px;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
background-color: rgba(255, 255, 255, 0.8);
|
||||
border-radius: 24px;
|
||||
box-shadow: 0 15px 40px rgba(0, 0, 0, 0.1);
|
||||
backdrop-filter: blur(15px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
|
||||
header {
|
||||
text-align: center;
|
||||
margin-bottom: 32px;
|
||||
padding-bottom: 24px;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
header h1 {
|
||||
background: linear-gradient(135deg, #1890ff, #ff7a45);
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
color: transparent;
|
||||
margin-bottom: 16px;
|
||||
font-size: 2.6rem;
|
||||
font-weight: 800;
|
||||
letter-spacing: -0.5px;
|
||||
text-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.update-time {
|
||||
color: #666;
|
||||
font-size: 0.9rem;
|
||||
background-color: rgba(255, 255, 255, 0.7);
|
||||
padding: 10px 20px;
|
||||
border-radius: 30px;
|
||||
display: inline-block;
|
||||
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.05);
|
||||
border: 1px solid rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
|
||||
.hot-list {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.hot-item {
|
||||
padding: 22px;
|
||||
margin-bottom: 20px;
|
||||
border-radius: 16px;
|
||||
background-color: white;
|
||||
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.06);
|
||||
transition: all 0.3s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border: 1px solid rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
|
||||
.hot-item:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08);
|
||||
border-color: rgba(64, 169, 255, 0.3);
|
||||
}
|
||||
|
||||
.hot-rank {
|
||||
font-size: 1.2rem;
|
||||
font-weight: bold;
|
||||
color: #4096ff;
|
||||
margin-right: 18px;
|
||||
min-width: 38px;
|
||||
text-align: center;
|
||||
background-color: rgba(64, 169, 255, 0.1);
|
||||
border-radius: 50%;
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.hot-rank.top-1 {
|
||||
background: linear-gradient(135deg, #ff4d4f, #ff7a45);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.hot-rank.top-2 {
|
||||
background: linear-gradient(135deg, #ff7a45, #ffa940);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.hot-rank.top-3 {
|
||||
background: linear-gradient(135deg, #ffa940, #ffec3d);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.hot-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.hot-title {
|
||||
font-size: 1.15rem;
|
||||
margin-bottom: 8px;
|
||||
color: #333;
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
line-height: 1.5;
|
||||
font-weight: 500;
|
||||
transition: color 0.2s ease;
|
||||
}
|
||||
|
||||
.hot-title:hover {
|
||||
color: #4096ff;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.loading {
|
||||
text-align: center;
|
||||
padding: 40px;
|
||||
color: #666;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
footer {
|
||||
text-align: center;
|
||||
margin-top: 30px;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.06);
|
||||
color: #666;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 1024px) and (min-width: 768px) {
|
||||
.container {
|
||||
max-width: 90%;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
header h1 {
|
||||
font-size: 2.2rem;
|
||||
}
|
||||
|
||||
.hot-item {
|
||||
padding: 18px;
|
||||
}
|
||||
|
||||
.hot-title {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
body {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 95%;
|
||||
margin: 12px auto;
|
||||
padding: 16px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
header {
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
|
||||
header h1 {
|
||||
font-size: 1.8rem;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.update-time {
|
||||
font-size: 0.85rem;
|
||||
padding: 6px 12px;
|
||||
}
|
||||
|
||||
.hot-item {
|
||||
padding: 16px;
|
||||
margin-bottom: 12px;
|
||||
border-radius: 10px;
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.hot-rank {
|
||||
font-size: 1.1rem;
|
||||
margin-right: 14px;
|
||||
min-width: 32px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.hot-title {
|
||||
font-size: 1rem;
|
||||
line-height: 1.5;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
footer {
|
||||
margin-top: 24px;
|
||||
padding-top: 16px;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.container {
|
||||
margin: 8px auto;
|
||||
padding: 14px;
|
||||
}
|
||||
|
||||
header h1 {
|
||||
font-size: 1.6rem;
|
||||
}
|
||||
|
||||
.hot-item {
|
||||
padding: 14px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.hot-rank {
|
||||
font-size: 1rem;
|
||||
margin-right: 12px;
|
||||
min-width: 30px;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.hot-title {
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* 减少动画以节省电池 */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.modern-gradient,
|
||||
.modern-gradient::before {
|
||||
animation: none;
|
||||
}
|
||||
|
||||
.modern-gradient {
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
rgba(64, 169, 255, 0.3) 0%,
|
||||
rgba(255, 175, 64, 0.2) 50%,
|
||||
rgba(255, 122, 69, 0.25) 100%
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,571 +0,0 @@
|
||||
/* 背景样式 */
|
||||
.background-container {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: -1;
|
||||
overflow: hidden;
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.modern-gradient {
|
||||
position: absolute;
|
||||
top: -50%;
|
||||
left: -50%;
|
||||
width: 200%;
|
||||
height: 200%;
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
rgba(168, 230, 207, 0.3) 0%,
|
||||
rgba(220, 237, 193, 0.25) 25%,
|
||||
rgba(200, 245, 200, 0.15) 50%,
|
||||
rgba(180, 235, 180, 0.25) 75%,
|
||||
rgba(168, 230, 207, 0.3) 100%
|
||||
);
|
||||
animation: gradient-flow 20s ease-in-out infinite;
|
||||
border-radius: 30% 70% 70% 30% / 30% 30% 70% 70%;
|
||||
}
|
||||
|
||||
.modern-gradient::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: radial-gradient(
|
||||
circle at 30% 70%,
|
||||
rgba(129, 199, 132, 0.4) 0%,
|
||||
transparent 50%
|
||||
), radial-gradient(
|
||||
circle at 70% 30%,
|
||||
rgba(165, 214, 167, 0.3) 0%,
|
||||
transparent 50%
|
||||
);
|
||||
animation: pulse-effect 15s ease-in-out infinite alternate;
|
||||
border-radius: 30% 70% 70% 30% / 30% 30% 70% 70%;
|
||||
}
|
||||
|
||||
@keyframes gradient-flow {
|
||||
0%, 100% {
|
||||
transform: rotate(0deg) scale(1);
|
||||
opacity: 0.8;
|
||||
}
|
||||
25% {
|
||||
transform: rotate(90deg) scale(1.1);
|
||||
opacity: 0.6;
|
||||
}
|
||||
50% {
|
||||
transform: rotate(180deg) scale(0.9);
|
||||
opacity: 0.9;
|
||||
}
|
||||
75% {
|
||||
transform: rotate(270deg) scale(1.05);
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pulse-effect {
|
||||
0% {
|
||||
transform: scale(1) rotate(0deg);
|
||||
opacity: 0.5;
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.2) rotate(180deg);
|
||||
opacity: 0.8;
|
||||
}
|
||||
100% {
|
||||
transform: scale(1) rotate(360deg);
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
/* 主样式 */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'PingFang SC', 'Microsoft YaHei', sans-serif;
|
||||
color: #333;
|
||||
background-color: #f8f9fa;
|
||||
position: relative;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* 几何装饰样式 */
|
||||
.title-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.geometric-decoration {
|
||||
font-size: 16px;
|
||||
color: #81c784;
|
||||
margin: 0 10px;
|
||||
font-weight: normal;
|
||||
letter-spacing: 3px;
|
||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.geometric-decoration.left {
|
||||
transform: rotate(-10deg);
|
||||
}
|
||||
|
||||
.geometric-decoration.right {
|
||||
transform: rotate(10deg);
|
||||
}
|
||||
|
||||
@keyframes float-effect {
|
||||
0% {
|
||||
transform: translateY(0) rotate(-10deg);
|
||||
}
|
||||
100% {
|
||||
transform: translateY(-5px) rotate(-8deg);
|
||||
}
|
||||
}
|
||||
|
||||
.update-time-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.time-decoration {
|
||||
font-size: 14px;
|
||||
color: #a5d6a7;
|
||||
margin: 0 8px;
|
||||
font-weight: normal;
|
||||
letter-spacing: 2px;
|
||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
opacity: 0.5;
|
||||
transform: scale(0.8);
|
||||
}
|
||||
50% {
|
||||
opacity: 1;
|
||||
transform: scale(1.2);
|
||||
}
|
||||
100% {
|
||||
opacity: 0.5;
|
||||
transform: scale(0.8);
|
||||
}
|
||||
}
|
||||
|
||||
.geometric-header, .geometric-footer {
|
||||
text-align: center;
|
||||
color: #a5d6a7;
|
||||
margin: 10px 0;
|
||||
font-size: 14px;
|
||||
letter-spacing: 2px;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.geometric-header {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.geometric-footer {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 24px;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
background-color: rgba(255, 255, 255, 0.85);
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.08);
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(129, 199, 132, 0.2);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.container::before,
|
||||
.container::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-color: #a5d6a7;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.container::before {
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
border-top: 3px solid;
|
||||
border-left: 3px solid;
|
||||
border-radius: 10px 0 0 0;
|
||||
}
|
||||
|
||||
.container::after {
|
||||
bottom: 10px;
|
||||
right: 10px;
|
||||
border-bottom: 3px solid;
|
||||
border-right: 3px solid;
|
||||
border-radius: 0 0 10px 0;
|
||||
}
|
||||
|
||||
header {
|
||||
text-align: center;
|
||||
margin-bottom: 28px;
|
||||
padding-bottom: 20px;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
header h1 {
|
||||
background: linear-gradient(135deg, #66bb6a, #81c784);
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
color: transparent;
|
||||
margin-bottom: 14px;
|
||||
font-size: 2.4rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.5px;
|
||||
}
|
||||
|
||||
.update-time {
|
||||
color: #666;
|
||||
font-size: 0.9rem;
|
||||
background-color: rgba(0, 0, 0, 0.03);
|
||||
padding: 8px 16px;
|
||||
border-radius: 24px;
|
||||
display: inline-block;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||||
position: relative;
|
||||
border: 1px dashed rgba(129, 199, 132, 0.3);
|
||||
}
|
||||
|
||||
.update-time::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -5px;
|
||||
left: -5px;
|
||||
right: -5px;
|
||||
bottom: -5px;
|
||||
border: 1px solid rgba(129, 199, 132, 0.3);
|
||||
border-radius: 28px;
|
||||
animation: pulse-border 2s infinite;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@keyframes pulse-border {
|
||||
0% {
|
||||
transform: scale(1);
|
||||
opacity: 0.7;
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.05);
|
||||
opacity: 0.3;
|
||||
}
|
||||
100% {
|
||||
transform: scale(1);
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
.hot-list {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.hot-item {
|
||||
padding: 20px;
|
||||
margin-bottom: 16px;
|
||||
border-radius: 12px;
|
||||
background-color: white;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.04);
|
||||
transition: all 0.3s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border: 1px solid rgba(0, 0, 0, 0.03);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.hot-item::before {
|
||||
content: '◆';
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: 10px;
|
||||
color: #a5d6a7;
|
||||
opacity: 0.15;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.hot-item::after {
|
||||
content: '◆';
|
||||
position: absolute;
|
||||
bottom: 5px;
|
||||
left: 10px;
|
||||
color: #a5d6a7;
|
||||
opacity: 0.15;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.even-item {
|
||||
border-left: 2px solid #81c784;
|
||||
}
|
||||
|
||||
.odd-item {
|
||||
border-right: 2px solid #81c784;
|
||||
}
|
||||
|
||||
.title-decoration {
|
||||
color: #81c784;
|
||||
font-weight: normal;
|
||||
margin-right: 5px;
|
||||
display: inline-block;
|
||||
transform: translateY(1px);
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.source-icon, .time-icon {
|
||||
color: #81c784;
|
||||
font-size: 14px;
|
||||
margin-right: 3px;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.hot-title {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.hot-stats {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.hot-item:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08);
|
||||
border-color: rgba(129, 199, 132, 0.4);
|
||||
}
|
||||
|
||||
.hot-rank {
|
||||
font-size: 1.2rem;
|
||||
font-weight: bold;
|
||||
color: #66bb6a;
|
||||
margin-right: 18px;
|
||||
min-width: 38px;
|
||||
text-align: center;
|
||||
background-color: rgba(129, 199, 132, 0.1);
|
||||
border-radius: 50%;
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.hot-rank.top-1 {
|
||||
background: linear-gradient(135deg, #4caf50, #66bb6a);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.hot-rank.top-2 {
|
||||
background: linear-gradient(135deg, #66bb6a, #81c784);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.hot-rank.top-3 {
|
||||
background: linear-gradient(135deg, #81c784, #a5d6a7);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.hot-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.hot-title {
|
||||
font-size: 1.15rem;
|
||||
margin-bottom: 8px;
|
||||
color: #333;
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
line-height: 1.5;
|
||||
font-weight: 500;
|
||||
transition: color 0.2s ease;
|
||||
}
|
||||
|
||||
.hot-title:hover {
|
||||
color: #66bb6a;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.loading {
|
||||
text-align: center;
|
||||
padding: 40px;
|
||||
color: #666;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
footer {
|
||||
text-align: center;
|
||||
margin-top: 40px;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.06);
|
||||
color: #999;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.footer-decoration {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin: 10px 0;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.geo-symbol {
|
||||
color: #a5d6a7;
|
||||
font-size: 14px;
|
||||
opacity: 0.5;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.geo-symbol:hover {
|
||||
opacity: 1;
|
||||
transform: scale(1.2) rotate(15deg);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 1024px) and (min-width: 768px) {
|
||||
.container {
|
||||
max-width: 90%;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
header h1 {
|
||||
font-size: 2.2rem;
|
||||
}
|
||||
|
||||
.hot-item {
|
||||
padding: 18px;
|
||||
}
|
||||
|
||||
.hot-title {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
body {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 95%;
|
||||
margin: 12px auto;
|
||||
padding: 16px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
header {
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
|
||||
header h1 {
|
||||
font-size: 1.8rem;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.update-time {
|
||||
font-size: 0.85rem;
|
||||
padding: 6px 12px;
|
||||
}
|
||||
|
||||
.hot-item {
|
||||
padding: 16px;
|
||||
margin-bottom: 12px;
|
||||
border-radius: 10px;
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.hot-rank {
|
||||
font-size: 1.1rem;
|
||||
margin-right: 14px;
|
||||
min-width: 32px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.hot-title {
|
||||
font-size: 1rem;
|
||||
line-height: 1.5;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
footer {
|
||||
margin-top: 24px;
|
||||
padding-top: 16px;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.container {
|
||||
margin: 8px auto;
|
||||
padding: 14px;
|
||||
}
|
||||
|
||||
header h1 {
|
||||
font-size: 1.6rem;
|
||||
}
|
||||
|
||||
.hot-item {
|
||||
padding: 14px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.hot-rank {
|
||||
font-size: 1rem;
|
||||
margin-right: 12px;
|
||||
min-width: 30px;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.hot-title {
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* 减少动画以节省电池 */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.modern-gradient,
|
||||
.modern-gradient::before {
|
||||
animation: none;
|
||||
}
|
||||
|
||||
.modern-gradient {
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
rgba(168, 230, 207, 0.25) 0%,
|
||||
rgba(200, 245, 200, 0.15) 50%,
|
||||
rgba(180, 235, 180, 0.2) 100%
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,62 +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="background-container">
|
||||
<div class="green-gradient"></div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<header>
|
||||
<div class="title-container">
|
||||
<div class="geometric-decoration left">◢ ◣ ▲</div>
|
||||
<h1>头条热搜榜</h1>
|
||||
<div class="geometric-decoration right">▼ ◥ ◤</div>
|
||||
</div>
|
||||
<div class="update-time-container">
|
||||
<span class="time-decoration">◇</span>
|
||||
<div class="update-time" id="updateTime"></div>
|
||||
<span class="time-decoration">◇</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<div class="geometric-header">
|
||||
<span>◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆</span>
|
||||
</div>
|
||||
<div class="hot-list" id="hotList">
|
||||
<div class="loading">加载中...</div>
|
||||
</div>
|
||||
<div class="geometric-footer">
|
||||
<span>◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆</span>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<div class="footer-decoration">
|
||||
<span class="geo-symbol">◆</span>
|
||||
<span class="geo-symbol">■</span>
|
||||
<span class="geo-symbol">▲</span>
|
||||
<span class="geo-symbol">●</span>
|
||||
<span class="geo-symbol">★</span>
|
||||
</div>
|
||||
<p>数据来源于头条热搜榜</p>
|
||||
<div class="footer-decoration">
|
||||
<span class="geo-symbol">★</span>
|
||||
<span class="geo-symbol">●</span>
|
||||
<span class="geo-symbol">▲</span>
|
||||
<span class="geo-symbol">■</span>
|
||||
<span class="geo-symbol">◆</span>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<script src="./js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,144 +0,0 @@
|
||||
/* 背景样式文件 - 独立管理所有背景相关样式 */
|
||||
|
||||
/* 页面主背景 */
|
||||
body {
|
||||
background: linear-gradient(135deg, #f8fffe 0%, #f0f9f4 50%, #e8f5e8 100%);
|
||||
background-attachment: fixed;
|
||||
background-size: cover;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* 背景装饰元素 */
|
||||
body::before {
|
||||
content: '';
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image:
|
||||
radial-gradient(circle at 20% 80%, rgba(168, 230, 207, 0.1) 0%, transparent 50%),
|
||||
radial-gradient(circle at 80% 20%, rgba(39, 174, 96, 0.08) 0%, transparent 50%),
|
||||
radial-gradient(circle at 40% 40%, rgba(46, 204, 113, 0.05) 0%, transparent 50%);
|
||||
pointer-events: none;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
/* 容器背景 */
|
||||
.container {
|
||||
background: rgba(255, 255, 255, 0.7);
|
||||
backdrop-filter: blur(10px);
|
||||
border-radius: 20px;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 8px 32px rgba(39, 174, 96, 0.1);
|
||||
}
|
||||
|
||||
/* 头部背景 */
|
||||
.header {
|
||||
background: linear-gradient(135deg, rgba(168, 230, 207, 0.2) 0%, rgba(39, 174, 96, 0.1) 100%);
|
||||
border-radius: 20px 20px 0 0;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.header::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -50%;
|
||||
left: -50%;
|
||||
width: 200%;
|
||||
height: 200%;
|
||||
background: radial-gradient(circle, rgba(39, 174, 96, 0.05) 0%, transparent 70%);
|
||||
animation: float 6s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes float {
|
||||
0%, 100% {
|
||||
transform: translate(0, 0) rotate(0deg);
|
||||
}
|
||||
50% {
|
||||
transform: translate(-10px, -10px) rotate(180deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* 热点项目背景 */
|
||||
.hot-item {
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
backdrop-filter: blur(5px);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.hot-item::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
background: radial-gradient(circle, rgba(168, 230, 207, 0.1) 0%, transparent 70%);
|
||||
border-radius: 50%;
|
||||
transform: translate(30px, -30px);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* 前三名特殊背景效果 */
|
||||
.hot-item:nth-child(1) {
|
||||
background: linear-gradient(135deg, rgba(255, 215, 0, 0.1) 0%, rgba(255, 255, 255, 0.9) 100%);
|
||||
}
|
||||
|
||||
.hot-item:nth-child(2) {
|
||||
background: linear-gradient(135deg, rgba(192, 192, 192, 0.1) 0%, rgba(255, 255, 255, 0.9) 100%);
|
||||
}
|
||||
|
||||
.hot-item:nth-child(3) {
|
||||
background: linear-gradient(135deg, rgba(205, 127, 50, 0.1) 0%, rgba(255, 255, 255, 0.9) 100%);
|
||||
}
|
||||
|
||||
/* 底部背景 */
|
||||
.footer {
|
||||
background: linear-gradient(135deg, rgba(168, 230, 207, 0.1) 0%, rgba(39, 174, 96, 0.05) 100%);
|
||||
border-radius: 0 0 20px 20px;
|
||||
}
|
||||
|
||||
/* 加载状态背景 */
|
||||
.loading {
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
border-radius: 12px;
|
||||
backdrop-filter: blur(5px);
|
||||
}
|
||||
|
||||
/* 错误信息背景 */
|
||||
.error-message {
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
border: 1px solid rgba(231, 76, 60, 0.2);
|
||||
border-radius: 12px;
|
||||
backdrop-filter: blur(5px);
|
||||
}
|
||||
|
||||
/* 响应式背景调整 */
|
||||
@media (max-width: 768px) {
|
||||
.container {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
||||
.header {
|
||||
border-radius: 16px 16px 0 0;
|
||||
}
|
||||
|
||||
.footer {
|
||||
border-radius: 0 0 16px 16px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1025px) {
|
||||
body::before {
|
||||
background-image:
|
||||
radial-gradient(circle at 15% 85%, rgba(168, 230, 207, 0.15) 0%, transparent 50%),
|
||||
radial-gradient(circle at 85% 15%, rgba(39, 174, 96, 0.12) 0%, transparent 50%),
|
||||
radial-gradient(circle at 50% 50%, rgba(46, 204, 113, 0.08) 0%, transparent 50%),
|
||||
radial-gradient(circle at 25% 25%, rgba(168, 230, 207, 0.06) 0%, transparent 50%);
|
||||
}
|
||||
}
|
||||
@@ -1,40 +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="styles.css">
|
||||
<link rel="stylesheet" href="background.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<header class="header">
|
||||
<h1 class="title">小红书热点榜单</h1>
|
||||
<p class="subtitle">实时热门话题,发现精彩内容</p>
|
||||
</header>
|
||||
|
||||
<main class="main-content">
|
||||
<div class="loading" id="loading">
|
||||
<div class="loading-spinner"></div>
|
||||
<p>正在加载热点数据...</p>
|
||||
</div>
|
||||
|
||||
<div class="error-message" id="error" style="display: none;">
|
||||
<p>数据加载失败,请稍后重试</p>
|
||||
<button class="retry-btn" onclick="loadData()">重新加载</button>
|
||||
</div>
|
||||
|
||||
<div class="hot-list" id="hotList" style="display: none;">
|
||||
<!-- 热点列表将通过JavaScript动态生成 -->
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="footer">
|
||||
<p class="update-time" id="updateTime"></p>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,180 +0,0 @@
|
||||
// 小红书热点榜单 JavaScript 逻辑
|
||||
|
||||
// DOM 元素
|
||||
const loadingEl = document.getElementById('loading');
|
||||
const errorEl = document.getElementById('error');
|
||||
const hotListEl = document.getElementById('hotList');
|
||||
const updateTimeEl = document.getElementById('updateTime');
|
||||
|
||||
// 页面加载完成后初始化
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
loadData();
|
||||
});
|
||||
|
||||
// 加载数据函数
|
||||
async function loadData() {
|
||||
try {
|
||||
showLoading();
|
||||
|
||||
// 从API接口获取数据
|
||||
const response = await fetch('https://60s.api.shumengya.top/v2/rednote');
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.code === 200 && data.data) {
|
||||
renderHotList(data.data);
|
||||
updateTime();
|
||||
showSuccess();
|
||||
} else {
|
||||
throw new Error('数据格式错误');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('加载数据失败:', error);
|
||||
showError();
|
||||
}
|
||||
}
|
||||
|
||||
// 显示加载状态
|
||||
function showLoading() {
|
||||
loadingEl.style.display = 'block';
|
||||
errorEl.style.display = 'none';
|
||||
hotListEl.style.display = 'none';
|
||||
}
|
||||
|
||||
// 显示错误状态
|
||||
function showError() {
|
||||
loadingEl.style.display = 'none';
|
||||
errorEl.style.display = 'block';
|
||||
hotListEl.style.display = 'none';
|
||||
}
|
||||
|
||||
// 显示成功状态
|
||||
function showSuccess() {
|
||||
loadingEl.style.display = 'none';
|
||||
errorEl.style.display = 'none';
|
||||
hotListEl.style.display = 'block';
|
||||
}
|
||||
|
||||
// 渲染热点列表
|
||||
function renderHotList(hotData) {
|
||||
hotListEl.innerHTML = '';
|
||||
|
||||
hotData.forEach((item, index) => {
|
||||
const hotItem = createHotItem(item, index);
|
||||
hotListEl.appendChild(hotItem);
|
||||
});
|
||||
}
|
||||
|
||||
// 创建热点项目元素
|
||||
function createHotItem(item, index) {
|
||||
const itemEl = document.createElement('div');
|
||||
itemEl.className = 'hot-item';
|
||||
|
||||
// 添加点击事件
|
||||
itemEl.addEventListener('click', () => {
|
||||
if (item.link) {
|
||||
window.open(item.link, '_blank');
|
||||
}
|
||||
});
|
||||
|
||||
// 构建HTML内容
|
||||
itemEl.innerHTML = `
|
||||
<div class="item-header">
|
||||
<div class="rank ${item.rank <= 3 ? 'top3' : ''}">${item.rank}</div>
|
||||
<div class="word-type">
|
||||
${item.work_type_icon ? `<img src="${item.work_type_icon}" alt="${item.word_type}" class="type-icon">` : ''}
|
||||
${item.word_type && item.word_type !== '无' ? `<span class="type-text type-${getTypeClass(item.word_type)}">${item.word_type}</span>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
<div class="item-content">
|
||||
<h3 class="item-title">${escapeHtml(item.title)}</h3>
|
||||
<p class="item-score">热度: ${item.score}</p>
|
||||
</div>
|
||||
`;
|
||||
|
||||
return itemEl;
|
||||
}
|
||||
|
||||
// 获取类型样式类名
|
||||
function getTypeClass(wordType) {
|
||||
switch (wordType) {
|
||||
case '热':
|
||||
return 'hot';
|
||||
case '新':
|
||||
return 'new';
|
||||
default:
|
||||
return 'default';
|
||||
}
|
||||
}
|
||||
|
||||
// HTML转义函数
|
||||
function escapeHtml(text) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
// 更新时间显示
|
||||
function updateTime() {
|
||||
const now = new Date();
|
||||
const timeString = now.toLocaleString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit'
|
||||
});
|
||||
updateTimeEl.textContent = `最后更新: ${timeString}`;
|
||||
}
|
||||
|
||||
// 添加页面可见性变化监听,当页面重新可见时刷新数据
|
||||
document.addEventListener('visibilitychange', function() {
|
||||
if (!document.hidden) {
|
||||
// 页面变为可见时,延迟1秒后刷新数据
|
||||
setTimeout(() => {
|
||||
loadData();
|
||||
}, 1000);
|
||||
}
|
||||
});
|
||||
|
||||
// 添加网络状态监听
|
||||
window.addEventListener('online', function() {
|
||||
// 网络恢复时自动重新加载
|
||||
setTimeout(() => {
|
||||
loadData();
|
||||
}, 500);
|
||||
});
|
||||
|
||||
// 添加错误处理
|
||||
window.addEventListener('error', function(e) {
|
||||
console.error('页面错误:', e.error);
|
||||
});
|
||||
|
||||
// 添加未处理的Promise拒绝监听
|
||||
window.addEventListener('unhandledrejection', function(e) {
|
||||
console.error('未处理的Promise拒绝:', e.reason);
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
// 添加触摸设备的优化
|
||||
if ('ontouchstart' in window) {
|
||||
// 为触摸设备添加触摸反馈
|
||||
document.addEventListener('touchstart', function() {}, { passive: true });
|
||||
}
|
||||
|
||||
// 添加键盘导航支持
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'F5' || (e.ctrlKey && e.key === 'r')) {
|
||||
e.preventDefault();
|
||||
loadData();
|
||||
}
|
||||
});
|
||||
|
||||
// 导出函数供全局使用
|
||||
window.loadData = loadData;
|
||||
@@ -1,299 +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: #2c3e50;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 16px;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 头部样式 */
|
||||
.header {
|
||||
text-align: center;
|
||||
padding: 24px 0;
|
||||
border-bottom: 2px solid #a8e6cf;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
color: #27ae60;
|
||||
margin-bottom: 8px;
|
||||
text-shadow: 0 2px 4px rgba(39, 174, 96, 0.1);
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 14px;
|
||||
color: #7f8c8d;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
/* 主内容区域 */
|
||||
.main-content {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* 加载状态 */
|
||||
.loading {
|
||||
text-align: center;
|
||||
padding: 60px 20px;
|
||||
color: #27ae60;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 3px solid #a8e6cf;
|
||||
border-top: 3px solid #27ae60;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin: 0 auto 16px;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* 错误信息 */
|
||||
.error-message {
|
||||
text-align: center;
|
||||
padding: 40px 20px;
|
||||
color: #e74c3c;
|
||||
}
|
||||
|
||||
.retry-btn {
|
||||
background: #27ae60;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 10px 20px;
|
||||
border-radius: 20px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
margin-top: 16px;
|
||||
transition: background 0.3s ease;
|
||||
}
|
||||
|
||||
.retry-btn:hover {
|
||||
background: #219a52;
|
||||
}
|
||||
|
||||
/* 热点列表 */
|
||||
.hot-list {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.hot-item {
|
||||
background: #ffffff;
|
||||
border: 1px solid #e8f5e8;
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 2px 8px rgba(39, 174, 96, 0.08);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.hot-item:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 16px rgba(39, 174, 96, 0.15);
|
||||
border-color: #a8e6cf;
|
||||
}
|
||||
|
||||
.hot-item::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 4px;
|
||||
height: 100%;
|
||||
background: linear-gradient(to bottom, #27ae60, #a8e6cf);
|
||||
}
|
||||
|
||||
.item-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.rank {
|
||||
background: linear-gradient(135deg, #27ae60, #2ecc71);
|
||||
color: white;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: 700;
|
||||
font-size: 14px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.rank.top3 {
|
||||
background: linear-gradient(135deg, #f39c12, #e67e22);
|
||||
}
|
||||
|
||||
.word-type {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.type-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.type-text {
|
||||
font-size: 12px;
|
||||
padding: 2px 8px;
|
||||
border-radius: 10px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.type-hot {
|
||||
background: #ffe6e6;
|
||||
color: #e74c3c;
|
||||
}
|
||||
|
||||
.type-new {
|
||||
background: #e6f3ff;
|
||||
color: #3498db;
|
||||
}
|
||||
|
||||
.item-content {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.item-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
line-height: 1.4;
|
||||
margin-bottom: 4px;
|
||||
cursor: pointer;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.item-title:hover {
|
||||
color: #27ae60;
|
||||
}
|
||||
|
||||
.item-score {
|
||||
font-size: 14px;
|
||||
color: #7f8c8d;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 底部 */
|
||||
.footer {
|
||||
text-align: center;
|
||||
padding: 24px 0;
|
||||
border-top: 1px solid #e8f5e8;
|
||||
margin-top: 32px;
|
||||
}
|
||||
|
||||
.update-time {
|
||||
font-size: 12px;
|
||||
color: #95a5a6;
|
||||
}
|
||||
|
||||
/* 手机端优化 (默认) */
|
||||
@media (max-width: 768px) {
|
||||
.container {
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.hot-item {
|
||||
padding: 14px;
|
||||
}
|
||||
|
||||
.item-title {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.rank {
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 平板端适配 */
|
||||
@media (min-width: 769px) and (max-width: 1024px) {
|
||||
.container {
|
||||
padding: 0 24px;
|
||||
}
|
||||
|
||||
.hot-list {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 电脑端适配 */
|
||||
@media (min-width: 1025px) {
|
||||
.container {
|
||||
padding: 0 32px;
|
||||
}
|
||||
|
||||
.hot-list {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 36px;
|
||||
}
|
||||
|
||||
.hot-item {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.item-title {
|
||||
font-size: 17px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 大屏幕优化 */
|
||||
@media (min-width: 1400px) {
|
||||
.hot-list {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
}
|
||||
@@ -1,166 +0,0 @@
|
||||
{
|
||||
"code": 200,
|
||||
"message": "获取成功。数据来自官方/权威源头,以确保稳定与实时。开源地址 https://github.com/vikiboss/60s,反馈群 595941841",
|
||||
"data": [
|
||||
{
|
||||
"rank": 1,
|
||||
"title": "九三阅兵",
|
||||
"score": "908.5w",
|
||||
"word_type": "热",
|
||||
"work_type_icon": "https://picasso-static.xiaohongshu.com/fe-platform/cfd317ff14757c7ede6ef5176ec487589565e49e.png",
|
||||
"link": "https://www.xiaohongshu.com/search_result?keyword=%E4%B9%9D%E4%B8%89%E9%98%85%E5%85%B5&type=51"
|
||||
},
|
||||
{
|
||||
"rank": 2,
|
||||
"title": "我镜头下的中式建筑美学",
|
||||
"score": "872.9w",
|
||||
"word_type": "新",
|
||||
"work_type_icon": "https://sns-img-qc.xhscdn.com/search/trends/icon/label/new/version/1",
|
||||
"link": "https://www.xiaohongshu.com/search_result?keyword=%E6%88%91%E9%95%9C%E5%A4%B4%E4%B8%8B%E7%9A%84%E4%B8%AD%E5%BC%8F%E5%BB%BA%E7%AD%91%E7%BE%8E%E5%AD%A6&type=51"
|
||||
},
|
||||
{
|
||||
"rank": 3,
|
||||
"title": "原来人机感才是出片的秘诀",
|
||||
"score": "754.4w",
|
||||
"word_type": "无",
|
||||
"work_type_icon": "",
|
||||
"link": "https://www.xiaohongshu.com/search_result?keyword=%E5%8E%9F%E6%9D%A5%E4%BA%BA%E6%9C%BA%E6%84%9F%E6%89%8D%E6%98%AF%E5%87%BA%E7%89%87%E7%9A%84%E7%A7%98%E8%AF%80&type=51"
|
||||
},
|
||||
{
|
||||
"rank": 4,
|
||||
"title": "我的二十年航天路",
|
||||
"score": "703.9w",
|
||||
"word_type": "热",
|
||||
"work_type_icon": "https://picasso-static.xiaohongshu.com/fe-platform/cfd317ff14757c7ede6ef5176ec487589565e49e.png",
|
||||
"link": "https://www.xiaohongshu.com/search_result?keyword=%E6%88%91%E7%9A%84%E4%BA%8C%E5%8D%81%E5%B9%B4%E8%88%AA%E5%A4%A9%E8%B7%AF&type=51"
|
||||
},
|
||||
{
|
||||
"rank": 5,
|
||||
"title": "用横图的方式打开香格里拉",
|
||||
"score": "458.5w",
|
||||
"word_type": "新",
|
||||
"work_type_icon": "https://sns-img-qc.xhscdn.com/search/trends/icon/label/new/version/1",
|
||||
"link": "https://www.xiaohongshu.com/search_result?keyword=%E7%94%A8%E6%A8%AA%E5%9B%BE%E7%9A%84%E6%96%B9%E5%BC%8F%E6%89%93%E5%BC%80%E9%A6%99%E6%A0%BC%E9%87%8C%E6%8B%89&type=51"
|
||||
},
|
||||
{
|
||||
"rank": 6,
|
||||
"title": "我拍到了苏州丰收的景象",
|
||||
"score": "392w",
|
||||
"word_type": "无",
|
||||
"work_type_icon": "",
|
||||
"link": "https://www.xiaohongshu.com/search_result?keyword=%E6%88%91%E6%8B%8D%E5%88%B0%E4%BA%86%E8%8B%8F%E5%B7%9E%E4%B8%B0%E6%94%B6%E7%9A%84%E6%99%AF%E8%B1%A1&type=51"
|
||||
},
|
||||
{
|
||||
"rank": 7,
|
||||
"title": "我拍下了960万平方公里的中国",
|
||||
"score": "390.7w",
|
||||
"word_type": "热",
|
||||
"work_type_icon": "https://picasso-static.xiaohongshu.com/fe-platform/cfd317ff14757c7ede6ef5176ec487589565e49e.png",
|
||||
"link": "https://www.xiaohongshu.com/search_result?keyword=%E6%88%91%E6%8B%8D%E4%B8%8B%E4%BA%86960%E4%B8%87%E5%B9%B3%E6%96%B9%E5%85%AC%E9%87%8C%E7%9A%84%E4%B8%AD%E5%9B%BD&type=51"
|
||||
},
|
||||
{
|
||||
"rank": 8,
|
||||
"title": "12岁冰岛少年的川剧变脸梦",
|
||||
"score": "389.7w",
|
||||
"word_type": "热",
|
||||
"work_type_icon": "https://picasso-static.xiaohongshu.com/fe-platform/cfd317ff14757c7ede6ef5176ec487589565e49e.png",
|
||||
"link": "https://www.xiaohongshu.com/search_result?keyword=12%E5%B2%81%E5%86%B0%E5%B2%9B%E5%B0%91%E5%B9%B4%E7%9A%84%E5%B7%9D%E5%89%A7%E5%8F%98%E8%84%B8%E6%A2%A6&type=51"
|
||||
},
|
||||
{
|
||||
"rank": 9,
|
||||
"title": "人生总要去一次阿勒泰吧",
|
||||
"score": "389.6w",
|
||||
"word_type": "新",
|
||||
"work_type_icon": "https://sns-img-qc.xhscdn.com/search/trends/icon/label/new/version/1",
|
||||
"link": "https://www.xiaohongshu.com/search_result?keyword=%E4%BA%BA%E7%94%9F%E6%80%BB%E8%A6%81%E5%8E%BB%E4%B8%80%E6%AC%A1%E9%98%BF%E5%8B%92%E6%B3%B0%E5%90%A7&type=51"
|
||||
},
|
||||
{
|
||||
"rank": 10,
|
||||
"title": "普通人的10年绘画进步史",
|
||||
"score": "389.4w",
|
||||
"word_type": "无",
|
||||
"work_type_icon": "",
|
||||
"link": "https://www.xiaohongshu.com/search_result?keyword=%E6%99%AE%E9%80%9A%E4%BA%BA%E7%9A%8410%E5%B9%B4%E7%BB%98%E7%94%BB%E8%BF%9B%E6%AD%A5%E5%8F%B2&type=51"
|
||||
},
|
||||
{
|
||||
"rank": 11,
|
||||
"title": "过生日不要忘记反转镜头",
|
||||
"score": "389.2w",
|
||||
"word_type": "热",
|
||||
"work_type_icon": "https://picasso-static.xiaohongshu.com/fe-platform/cfd317ff14757c7ede6ef5176ec487589565e49e.png",
|
||||
"link": "https://www.xiaohongshu.com/search_result?keyword=%E8%BF%87%E7%94%9F%E6%97%A5%E4%B8%8D%E8%A6%81%E5%BF%98%E8%AE%B0%E5%8F%8D%E8%BD%AC%E9%95%9C%E5%A4%B4&type=51"
|
||||
},
|
||||
{
|
||||
"rank": 12,
|
||||
"title": "低卡又解馋的报恩零食",
|
||||
"score": "389.2w",
|
||||
"word_type": "无",
|
||||
"work_type_icon": "",
|
||||
"link": "https://www.xiaohongshu.com/search_result?keyword=%E4%BD%8E%E5%8D%A1%E5%8F%88%E8%A7%A3%E9%A6%8B%E7%9A%84%E6%8A%A5%E6%81%A9%E9%9B%B6%E9%A3%9F&type=51"
|
||||
},
|
||||
{
|
||||
"rank": 13,
|
||||
"title": "和爸妈去旅游 我是水印",
|
||||
"score": "389w",
|
||||
"word_type": "新",
|
||||
"work_type_icon": "https://sns-img-qc.xhscdn.com/search/trends/icon/label/new/version/1",
|
||||
"link": "https://www.xiaohongshu.com/search_result?keyword=%E5%92%8C%E7%88%B8%E5%A6%88%E5%8E%BB%E6%97%85%E6%B8%B8%20%E6%88%91%E6%98%AF%E6%B0%B4%E5%8D%B0&type=51"
|
||||
},
|
||||
{
|
||||
"rank": 14,
|
||||
"title": "一种很新的镜子拍谷法出现了",
|
||||
"score": "389w",
|
||||
"word_type": "热",
|
||||
"work_type_icon": "https://picasso-static.xiaohongshu.com/fe-platform/cfd317ff14757c7ede6ef5176ec487589565e49e.png",
|
||||
"link": "https://www.xiaohongshu.com/search_result?keyword=%E4%B8%80%E7%A7%8D%E5%BE%88%E6%96%B0%E7%9A%84%E9%95%9C%E5%AD%90%E6%8B%8D%E8%B0%B7%E6%B3%95%E5%87%BA%E7%8E%B0%E4%BA%86&type=51"
|
||||
},
|
||||
{
|
||||
"rank": 15,
|
||||
"title": "二次构图带来的故事感",
|
||||
"score": "389w",
|
||||
"word_type": "无",
|
||||
"work_type_icon": "",
|
||||
"link": "https://www.xiaohongshu.com/search_result?keyword=%E4%BA%8C%E6%AC%A1%E6%9E%84%E5%9B%BE%E5%B8%A6%E6%9D%A5%E7%9A%84%E6%95%85%E4%BA%8B%E6%84%9F&type=51"
|
||||
},
|
||||
{
|
||||
"rank": 16,
|
||||
"title": "当我在老动画片里找美妆灵感",
|
||||
"score": "389w",
|
||||
"word_type": "无",
|
||||
"work_type_icon": "",
|
||||
"link": "https://www.xiaohongshu.com/search_result?keyword=%E5%BD%93%E6%88%91%E5%9C%A8%E8%80%81%E5%8A%A8%E7%94%BB%E7%89%87%E9%87%8C%E6%89%BE%E7%BE%8E%E5%A6%86%E7%81%B5%E6%84%9F&type=51"
|
||||
},
|
||||
{
|
||||
"rank": 17,
|
||||
"title": "在蓝调时刻起舞告别夏天",
|
||||
"score": "389w",
|
||||
"word_type": "热",
|
||||
"work_type_icon": "https://picasso-static.xiaohongshu.com/fe-platform/cfd317ff14757c7ede6ef5176ec487589565e49e.png",
|
||||
"link": "https://www.xiaohongshu.com/search_result?keyword=%E5%9C%A8%E8%93%9D%E8%B0%83%E6%97%B6%E5%88%BB%E8%B5%B7%E8%88%9E%E5%91%8A%E5%88%AB%E5%A4%8F%E5%A4%A9&type=51"
|
||||
},
|
||||
{
|
||||
"rank": 18,
|
||||
"title": "人生建议:去看一次鱼灯巡游",
|
||||
"score": "389w",
|
||||
"word_type": "无",
|
||||
"work_type_icon": "",
|
||||
"link": "https://www.xiaohongshu.com/search_result?keyword=%E4%BA%BA%E7%94%9F%E5%BB%BA%E8%AE%AE%EF%BC%9A%E5%8E%BB%E7%9C%8B%E4%B8%80%E6%AC%A1%E9%B1%BC%E7%81%AF%E5%B7%A1%E6%B8%B8&type=51"
|
||||
},
|
||||
{
|
||||
"rank": 19,
|
||||
"title": "夜晚的树是大自然送给天空的星星",
|
||||
"score": "389w",
|
||||
"word_type": "新",
|
||||
"work_type_icon": "https://sns-img-qc.xhscdn.com/search/trends/icon/label/new/version/1",
|
||||
"link": "https://www.xiaohongshu.com/search_result?keyword=%E5%A4%9C%E6%99%9A%E7%9A%84%E6%A0%91%E6%98%AF%E5%A4%A7%E8%87%AA%E7%84%B6%E9%80%81%E7%BB%99%E5%A4%A9%E7%A9%BA%E7%9A%84%E6%98%9F%E6%98%9F&type=51"
|
||||
},
|
||||
{
|
||||
"rank": 20,
|
||||
"title": "欢迎收看老师开学的一天",
|
||||
"score": "389w",
|
||||
"word_type": "无",
|
||||
"work_type_icon": "",
|
||||
"link": "https://www.xiaohongshu.com/search_result?keyword=%E6%AC%A2%E8%BF%8E%E6%94%B6%E7%9C%8B%E8%80%81%E5%B8%88%E5%BC%80%E5%AD%A6%E7%9A%84%E4%B8%80%E5%A4%A9&type=51"
|
||||
}
|
||||
]
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user