feat: 完成全站UI优化 - 科技感/未来风设计
- 前台页面全面升级为Tailwind CSS框架 - 引入Google Fonts (Space Grotesk, Noto Sans) - 主色调更新为#25c0f4 (cyan blue) - 实现玻璃态效果和渐变背景 - 优化首页网格卡片布局和悬停动画 - 优化详情页双栏布局和渐变Logo光晕 - 优化管理员登录页,添加科技网格背景 - Flask-Admin后台完整深色主题 - 统一Material Symbols图标系统 - 网站自动抓取功能界面优化 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
22
.claude/settings.local.json
Normal file
22
.claude/settings.local.json
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"permissions": {
|
||||||
|
"allow": [
|
||||||
|
"Bash(if [ -d \".git\" ])",
|
||||||
|
"Bash(then echo \"Git repository exists\")",
|
||||||
|
"Bash(else echo \"No git repository\")",
|
||||||
|
"Bash(fi)",
|
||||||
|
"Bash(python:*)",
|
||||||
|
"Bash(python3:*)",
|
||||||
|
"Bash(py test_db.py:*)",
|
||||||
|
"Bash(where:*)",
|
||||||
|
"Bash(/c/Users/linha/AppData/Local/Microsoft/WindowsApps/python test_db.py)",
|
||||||
|
"Bash(pip install:*)",
|
||||||
|
"Bash(pip uninstall:*)",
|
||||||
|
"Bash(tasklist:*)",
|
||||||
|
"Bash(findstr:*)",
|
||||||
|
"Bash(dir:*)",
|
||||||
|
"Bash(git init:*)",
|
||||||
|
"Bash(git add:*)"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
12
.env.example
Normal file
12
.env.example
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# 数据库配置
|
||||||
|
DB_HOST=localhost
|
||||||
|
DB_PORT=3306
|
||||||
|
DB_USER=root
|
||||||
|
DB_PASSWORD=your_password
|
||||||
|
DB_NAME=ai_nav
|
||||||
|
|
||||||
|
# 安全配置
|
||||||
|
SECRET_KEY=your-secret-key-here
|
||||||
|
|
||||||
|
# 运行环境 (development/production)
|
||||||
|
FLASK_ENV=development
|
||||||
64
.gitignore
vendored
Normal file
64
.gitignore
vendored
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
# Python
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
*.so
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
|
||||||
|
# 虚拟环境
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
env/
|
||||||
|
.venv
|
||||||
|
|
||||||
|
# Flask
|
||||||
|
instance/
|
||||||
|
.webassets-cache
|
||||||
|
|
||||||
|
# 环境变量
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
|
||||||
|
# 数据库
|
||||||
|
*.db
|
||||||
|
*.sqlite
|
||||||
|
*.sqlite3
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
|
||||||
|
# 日志
|
||||||
|
*.log
|
||||||
|
logs/
|
||||||
|
|
||||||
|
# 系统文件
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# 上传文件
|
||||||
|
static/uploads/*
|
||||||
|
!static/uploads/.gitkeep
|
||||||
|
|
||||||
|
# 临时文件
|
||||||
|
*.tmp
|
||||||
|
*.bak
|
||||||
|
*.cache
|
||||||
219
README.md
Normal file
219
README.md
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
# AI工具导航网站
|
||||||
|
|
||||||
|
一个简洁美观的AI产品导航网站,用于展示和管理各类AI工具和应用。
|
||||||
|
|
||||||
|
## 功能特点
|
||||||
|
|
||||||
|
- ✅ 按标签分类展示AI工具
|
||||||
|
- ✅ 卡片式设计,美观易用
|
||||||
|
- ✅ 详细的工具介绍页面
|
||||||
|
- ✅ 完善的后台管理系统
|
||||||
|
- ✅ SEO友好的URL结构
|
||||||
|
- ✅ 响应式设计,支持移动端
|
||||||
|
- ✅ 浏览量统计
|
||||||
|
|
||||||
|
## 技术栈
|
||||||
|
|
||||||
|
- **后端**: Flask 3.0 + Python 3.8+
|
||||||
|
- **数据库**: MySQL 5.7+
|
||||||
|
- **前端**: Bootstrap 5 + Jinja2模板
|
||||||
|
- **管理后台**: Flask-Admin
|
||||||
|
- **用户认证**: Flask-Login
|
||||||
|
|
||||||
|
## 项目结构
|
||||||
|
|
||||||
|
```
|
||||||
|
zjpb/
|
||||||
|
├── app.py # Flask应用主文件
|
||||||
|
├── config.py # 配置文件
|
||||||
|
├── models.py # 数据库模型
|
||||||
|
├── init_db.py # 数据库初始化脚本
|
||||||
|
├── requirements.txt # Python依赖
|
||||||
|
├── .env.example # 环境变量示例
|
||||||
|
├── templates/ # HTML模板
|
||||||
|
│ ├── base.html
|
||||||
|
│ ├── index.html # 首页
|
||||||
|
│ ├── detail.html # 详情页
|
||||||
|
│ └── admin_login.html # 登录页
|
||||||
|
├── static/ # 静态资源
|
||||||
|
│ ├── css/
|
||||||
|
│ │ └── style.css
|
||||||
|
│ ├── js/
|
||||||
|
│ │ └── main.js
|
||||||
|
│ └── images/
|
||||||
|
└── migrations/ # 数据库迁移文件
|
||||||
|
```
|
||||||
|
|
||||||
|
## 快速开始
|
||||||
|
|
||||||
|
### 1. 环境准备
|
||||||
|
|
||||||
|
确保已安装以下软件:
|
||||||
|
- Python 3.8+
|
||||||
|
- MySQL 5.7+
|
||||||
|
- pip
|
||||||
|
|
||||||
|
### 2. 安装依赖
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 创建虚拟环境(推荐)
|
||||||
|
python -m venv venv
|
||||||
|
|
||||||
|
# 激活虚拟环境
|
||||||
|
# Windows:
|
||||||
|
venv\Scripts\activate
|
||||||
|
# Linux/Mac:
|
||||||
|
source venv/bin/activate
|
||||||
|
|
||||||
|
# 安装依赖
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 配置数据库
|
||||||
|
|
||||||
|
1. 在MySQL中创建数据库:
|
||||||
|
```sql
|
||||||
|
CREATE DATABASE ai_nav CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 复制环境变量配置文件:
|
||||||
|
```bash
|
||||||
|
cp .env.example .env
|
||||||
|
```
|
||||||
|
|
||||||
|
3. 编辑 `.env` 文件,修改数据库配置:
|
||||||
|
```
|
||||||
|
DB_HOST=localhost
|
||||||
|
DB_PORT=3306
|
||||||
|
DB_USER=root
|
||||||
|
DB_PASSWORD=your_password
|
||||||
|
DB_NAME=ai_nav
|
||||||
|
SECRET_KEY=your-secret-key-here
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 初始化数据库
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python init_db.py
|
||||||
|
```
|
||||||
|
|
||||||
|
这将创建所有数据表,并添加示例数据和默认管理员账号。
|
||||||
|
|
||||||
|
### 5. 运行应用
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python app.py
|
||||||
|
```
|
||||||
|
|
||||||
|
访问 `http://localhost:5000` 查看网站。
|
||||||
|
|
||||||
|
## 管理后台
|
||||||
|
|
||||||
|
### 访问后台
|
||||||
|
|
||||||
|
- 后台地址: `http://localhost:5000/admin`
|
||||||
|
- 登录页面: `http://localhost:5000/admin/login`
|
||||||
|
|
||||||
|
### 默认管理员账号
|
||||||
|
|
||||||
|
```
|
||||||
|
用户名: admin
|
||||||
|
密码: admin123
|
||||||
|
```
|
||||||
|
|
||||||
|
**⚠️ 重要**: 首次登录后请立即修改默认密码!
|
||||||
|
|
||||||
|
### 后台功能
|
||||||
|
|
||||||
|
- **网站管理**: 添加、编辑、删除AI工具
|
||||||
|
- **标签管理**: 管理分类标签
|
||||||
|
- **管理员管理**: 添加和管理管理员账号
|
||||||
|
|
||||||
|
## 宝塔面板部署
|
||||||
|
|
||||||
|
### 1. 安装Python环境
|
||||||
|
|
||||||
|
在宝塔面板中安装Python项目管理器,选择Python 3.8+版本。
|
||||||
|
|
||||||
|
### 2. 上传项目
|
||||||
|
|
||||||
|
将项目文件上传到服务器,例如 `/www/wwwroot/ai_nav`
|
||||||
|
|
||||||
|
### 3. 配置项目
|
||||||
|
|
||||||
|
1. 在宝塔面板中创建Python项目
|
||||||
|
2. 项目路径: `/www/wwwroot/ai_nav`
|
||||||
|
3. 启动文件: `app.py`
|
||||||
|
4. 端口: 5000(或其他可用端口)
|
||||||
|
|
||||||
|
### 4. 安装依赖
|
||||||
|
|
||||||
|
在项目目录下执行:
|
||||||
|
```bash
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. 配置反向代理
|
||||||
|
|
||||||
|
在宝塔面板的网站设置中配置反向代理:
|
||||||
|
- 目标URL: `http://127.0.0.1:5000`
|
||||||
|
- 启用缓存和gzip压缩
|
||||||
|
|
||||||
|
### 6. 配置SSL证书(可选)
|
||||||
|
|
||||||
|
为网站配置SSL证书以启用HTTPS。
|
||||||
|
|
||||||
|
## 使用说明
|
||||||
|
|
||||||
|
### 添加新网站
|
||||||
|
|
||||||
|
1. 登录后台管理系统
|
||||||
|
2. 点击"网站管理" -> "Create"
|
||||||
|
3. 填写网站信息:
|
||||||
|
- 网站名称
|
||||||
|
- URL地址
|
||||||
|
- URL别名(用于SEO友好的URL)
|
||||||
|
- Logo图片URL
|
||||||
|
- 简短描述
|
||||||
|
- 详细介绍
|
||||||
|
- 主要功能
|
||||||
|
- 选择标签
|
||||||
|
- 排序权重(数字越大越靠前)
|
||||||
|
|
||||||
|
### 管理标签
|
||||||
|
|
||||||
|
1. 登录后台
|
||||||
|
2. 点击"标签管理"
|
||||||
|
3. 可以添加、编辑或删除标签
|
||||||
|
4. 为标签设置图标(Font Awesome类名)
|
||||||
|
|
||||||
|
## 开发计划
|
||||||
|
|
||||||
|
- [ ] 搜索功能
|
||||||
|
- [ ] 用户评论和评分
|
||||||
|
- [ ] 关联新闻搜索(2.0版本)
|
||||||
|
- [ ] 数据统计和分析
|
||||||
|
- [ ] API接口
|
||||||
|
- [ ] 网站收藏功能
|
||||||
|
|
||||||
|
## 常见问题
|
||||||
|
|
||||||
|
### 1. 数据库连接失败
|
||||||
|
|
||||||
|
检查 `.env` 文件中的数据库配置是否正确,确保MySQL服务正在运行。
|
||||||
|
|
||||||
|
### 2. 启动时出现端口占用
|
||||||
|
|
||||||
|
修改 `app.py` 中的端口号,或关闭占用5000端口的其他程序。
|
||||||
|
|
||||||
|
### 3. 静态资源加载失败
|
||||||
|
|
||||||
|
检查 `static` 目录权限,确保Web服务器有读取权限。
|
||||||
|
|
||||||
|
## 许可证
|
||||||
|
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
## 联系方式
|
||||||
|
|
||||||
|
如有问题或建议,请提交Issue。
|
||||||
243
app.py
Normal file
243
app.py
Normal file
@@ -0,0 +1,243 @@
|
|||||||
|
import os
|
||||||
|
from flask import Flask, render_template, redirect, url_for, request, flash, jsonify
|
||||||
|
from flask_login import LoginManager, login_user, logout_user, login_required, current_user
|
||||||
|
from flask_admin import Admin, AdminIndexView
|
||||||
|
from flask_admin.contrib.sqla import ModelView
|
||||||
|
from datetime import datetime
|
||||||
|
from config import config
|
||||||
|
from models import db, Site, Tag, Admin as AdminModel
|
||||||
|
from utils.website_fetcher import WebsiteFetcher
|
||||||
|
|
||||||
|
def create_app(config_name='default'):
|
||||||
|
"""应用工厂函数"""
|
||||||
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
# 加载配置
|
||||||
|
app.config.from_object(config[config_name])
|
||||||
|
|
||||||
|
# 初始化数据库
|
||||||
|
db.init_app(app)
|
||||||
|
|
||||||
|
# 初始化登录管理
|
||||||
|
login_manager = LoginManager()
|
||||||
|
login_manager.init_app(app)
|
||||||
|
login_manager.login_view = 'admin_login'
|
||||||
|
login_manager.login_message = '请先登录'
|
||||||
|
|
||||||
|
@login_manager.user_loader
|
||||||
|
def load_user(user_id):
|
||||||
|
return AdminModel.query.get(int(user_id))
|
||||||
|
|
||||||
|
# ========== 前台路由 ==========
|
||||||
|
@app.route('/')
|
||||||
|
def index():
|
||||||
|
"""首页"""
|
||||||
|
# 获取所有启用的标签
|
||||||
|
tags = Tag.query.order_by(Tag.sort_order.desc(), Tag.id).all()
|
||||||
|
|
||||||
|
# 获取筛选的标签
|
||||||
|
tag_slug = request.args.get('tag')
|
||||||
|
selected_tag = None
|
||||||
|
|
||||||
|
if tag_slug:
|
||||||
|
selected_tag = Tag.query.filter_by(slug=tag_slug).first()
|
||||||
|
if selected_tag:
|
||||||
|
sites = Site.query.filter(
|
||||||
|
Site.is_active == True,
|
||||||
|
Site.tags.contains(selected_tag)
|
||||||
|
).order_by(Site.sort_order.desc(), Site.id.desc()).all()
|
||||||
|
else:
|
||||||
|
sites = []
|
||||||
|
else:
|
||||||
|
# 获取所有启用的网站
|
||||||
|
sites = Site.query.filter_by(is_active=True).order_by(
|
||||||
|
Site.sort_order.desc(), Site.id.desc()
|
||||||
|
).all()
|
||||||
|
|
||||||
|
return render_template('index.html', sites=sites, tags=tags, selected_tag=selected_tag)
|
||||||
|
|
||||||
|
@app.route('/site/<slug>')
|
||||||
|
def site_detail(slug):
|
||||||
|
"""网站详情页"""
|
||||||
|
site = Site.query.filter_by(slug=slug, is_active=True).first_or_404()
|
||||||
|
|
||||||
|
# 增加浏览次数
|
||||||
|
site.view_count += 1
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
return render_template('detail.html', site=site)
|
||||||
|
|
||||||
|
# ========== 后台登录路由 ==========
|
||||||
|
@app.route('/admin/login', methods=['GET', 'POST'])
|
||||||
|
def admin_login():
|
||||||
|
"""管理员登录"""
|
||||||
|
if current_user.is_authenticated:
|
||||||
|
return redirect(url_for('admin.index'))
|
||||||
|
|
||||||
|
if request.method == 'POST':
|
||||||
|
username = request.form.get('username')
|
||||||
|
password = request.form.get('password')
|
||||||
|
|
||||||
|
admin = AdminModel.query.filter_by(username=username).first()
|
||||||
|
|
||||||
|
if admin and admin.check_password(password) and admin.is_active:
|
||||||
|
login_user(admin)
|
||||||
|
admin.last_login = datetime.now()
|
||||||
|
db.session.commit()
|
||||||
|
return redirect(url_for('admin.index'))
|
||||||
|
else:
|
||||||
|
flash('用户名或密码错误', 'error')
|
||||||
|
|
||||||
|
return render_template('admin_login.html')
|
||||||
|
|
||||||
|
@app.route('/admin/logout')
|
||||||
|
@login_required
|
||||||
|
def admin_logout():
|
||||||
|
"""管理员登出"""
|
||||||
|
logout_user()
|
||||||
|
return redirect(url_for('index'))
|
||||||
|
|
||||||
|
# ========== API路由 ==========
|
||||||
|
@app.route('/api/fetch-website-info', methods=['POST'])
|
||||||
|
@login_required
|
||||||
|
def fetch_website_info():
|
||||||
|
"""抓取网站信息API"""
|
||||||
|
try:
|
||||||
|
data = request.get_json()
|
||||||
|
url = data.get('url', '').strip()
|
||||||
|
|
||||||
|
if not url:
|
||||||
|
return jsonify({
|
||||||
|
'success': False,
|
||||||
|
'message': '请提供网站URL'
|
||||||
|
}), 400
|
||||||
|
|
||||||
|
# 创建抓取器
|
||||||
|
fetcher = WebsiteFetcher(timeout=15)
|
||||||
|
|
||||||
|
# 抓取网站信息
|
||||||
|
info = fetcher.fetch_website_info(url)
|
||||||
|
|
||||||
|
if not info:
|
||||||
|
return jsonify({
|
||||||
|
'success': False,
|
||||||
|
'message': '无法获取网站信息,请检查URL是否正确或手动填写'
|
||||||
|
})
|
||||||
|
|
||||||
|
# 下载Logo(如果有)
|
||||||
|
logo_path = None
|
||||||
|
if info.get('logo_url'):
|
||||||
|
logo_path = fetcher.download_logo(info['logo_url'])
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'success': True,
|
||||||
|
'data': {
|
||||||
|
'name': info.get('name', ''),
|
||||||
|
'description': info.get('description', ''),
|
||||||
|
'logo': logo_path or info.get('logo_url', '')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({
|
||||||
|
'success': False,
|
||||||
|
'message': f'抓取失败: {str(e)}'
|
||||||
|
}), 500
|
||||||
|
|
||||||
|
# ========== Flask-Admin 配置 ==========
|
||||||
|
class SecureModelView(ModelView):
|
||||||
|
"""需要登录的模型视图"""
|
||||||
|
def is_accessible(self):
|
||||||
|
return current_user.is_authenticated
|
||||||
|
|
||||||
|
def inaccessible_callback(self, name, **kwargs):
|
||||||
|
return redirect(url_for('admin_login'))
|
||||||
|
|
||||||
|
class SecureAdminIndexView(AdminIndexView):
|
||||||
|
"""需要登录的管理首页"""
|
||||||
|
def is_accessible(self):
|
||||||
|
return current_user.is_authenticated
|
||||||
|
|
||||||
|
def inaccessible_callback(self, name, **kwargs):
|
||||||
|
return redirect(url_for('admin_login'))
|
||||||
|
|
||||||
|
# 网站管理视图
|
||||||
|
class SiteAdmin(SecureModelView):
|
||||||
|
# 自定义模板
|
||||||
|
create_template = 'admin/site/create.html'
|
||||||
|
edit_template = 'admin/site/edit.html'
|
||||||
|
|
||||||
|
column_list = ['id', 'name', 'url', 'slug', 'is_active', 'view_count', 'created_at']
|
||||||
|
column_searchable_list = ['name', 'url', 'description']
|
||||||
|
column_filters = ['is_active', 'tags']
|
||||||
|
column_labels = {
|
||||||
|
'id': 'ID',
|
||||||
|
'name': '网站名称',
|
||||||
|
'url': 'URL',
|
||||||
|
'slug': 'URL别名',
|
||||||
|
'logo': 'Logo',
|
||||||
|
'short_desc': '简短描述',
|
||||||
|
'description': '详细介绍',
|
||||||
|
'features': '主要功能',
|
||||||
|
'is_active': '是否启用',
|
||||||
|
'view_count': '浏览次数',
|
||||||
|
'sort_order': '排序权重',
|
||||||
|
'tags': '标签',
|
||||||
|
'created_at': '创建时间',
|
||||||
|
'updated_at': '更新时间'
|
||||||
|
}
|
||||||
|
form_columns = ['name', 'url', 'slug', 'logo', 'short_desc', 'description', 'features', 'tags', 'is_active', 'sort_order']
|
||||||
|
|
||||||
|
# 标签管理视图
|
||||||
|
class TagAdmin(SecureModelView):
|
||||||
|
column_list = ['id', 'name', 'slug', 'description', 'sort_order']
|
||||||
|
column_searchable_list = ['name', 'description']
|
||||||
|
column_labels = {
|
||||||
|
'id': 'ID',
|
||||||
|
'name': '标签名称',
|
||||||
|
'slug': 'URL别名',
|
||||||
|
'description': '标签描述',
|
||||||
|
'icon': '图标',
|
||||||
|
'sort_order': '排序权重',
|
||||||
|
'created_at': '创建时间'
|
||||||
|
}
|
||||||
|
form_columns = ['name', 'slug', 'description', 'icon', 'sort_order']
|
||||||
|
|
||||||
|
# 管理员视图
|
||||||
|
class AdminAdmin(SecureModelView):
|
||||||
|
column_list = ['id', 'username', 'email', 'is_active', 'last_login', 'created_at']
|
||||||
|
column_searchable_list = ['username', 'email']
|
||||||
|
column_filters = ['is_active']
|
||||||
|
column_labels = {
|
||||||
|
'id': 'ID',
|
||||||
|
'username': '用户名',
|
||||||
|
'email': '邮箱',
|
||||||
|
'is_active': '是否启用',
|
||||||
|
'created_at': '创建时间',
|
||||||
|
'last_login': '最后登录'
|
||||||
|
}
|
||||||
|
form_columns = ['username', 'email', 'is_active']
|
||||||
|
|
||||||
|
def on_model_change(self, form, model, is_created):
|
||||||
|
# 如果是新建管理员,设置默认密码
|
||||||
|
if is_created:
|
||||||
|
model.set_password('admin123') # 默认密码
|
||||||
|
|
||||||
|
# 初始化 Flask-Admin
|
||||||
|
admin = Admin(
|
||||||
|
app,
|
||||||
|
name='ZJPB 焦提示词 - 后台管理',
|
||||||
|
template_mode='bootstrap4',
|
||||||
|
index_view=SecureAdminIndexView(),
|
||||||
|
base_template='admin/custom_base.html'
|
||||||
|
)
|
||||||
|
|
||||||
|
admin.add_view(SiteAdmin(Site, db.session, name='网站管理'))
|
||||||
|
admin.add_view(TagAdmin(Tag, db.session, name='标签管理'))
|
||||||
|
admin.add_view(AdminAdmin(AdminModel, db.session, name='管理员', endpoint='admin_users'))
|
||||||
|
|
||||||
|
return app
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
app = create_app(os.getenv('FLASK_ENV', 'development'))
|
||||||
|
app.run(host='0.0.0.0', port=5000, debug=True)
|
||||||
46
config.py
Normal file
46
config.py
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import os
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
# 加载环境变量
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
"""基础配置"""
|
||||||
|
# 密钥配置
|
||||||
|
SECRET_KEY = os.environ.get('SECRET_KEY') or 'dev-secret-key-change-in-production'
|
||||||
|
|
||||||
|
# 数据库配置
|
||||||
|
DB_HOST = os.environ.get('DB_HOST') or 'localhost'
|
||||||
|
DB_PORT = os.environ.get('DB_PORT') or '3306'
|
||||||
|
DB_USER = os.environ.get('DB_USER') or 'root'
|
||||||
|
DB_PASSWORD = os.environ.get('DB_PASSWORD') or ''
|
||||||
|
DB_NAME = os.environ.get('DB_NAME') or 'ai_nav'
|
||||||
|
|
||||||
|
SQLALCHEMY_DATABASE_URI = f'mysql+pymysql://{DB_USER}:{DB_PASSWORD}@{DB_HOST}:{DB_PORT}/{DB_NAME}?charset=utf8mb4'
|
||||||
|
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
||||||
|
SQLALCHEMY_ECHO = False
|
||||||
|
|
||||||
|
# 分页配置
|
||||||
|
SITES_PER_PAGE = 20
|
||||||
|
|
||||||
|
# 上传文件配置
|
||||||
|
UPLOAD_FOLDER = 'static/uploads'
|
||||||
|
MAX_CONTENT_LENGTH = 5 * 1024 * 1024 # 5MB
|
||||||
|
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif', 'webp'}
|
||||||
|
|
||||||
|
class DevelopmentConfig(Config):
|
||||||
|
"""开发环境配置"""
|
||||||
|
DEBUG = True
|
||||||
|
SQLALCHEMY_ECHO = True
|
||||||
|
|
||||||
|
class ProductionConfig(Config):
|
||||||
|
"""生产环境配置"""
|
||||||
|
DEBUG = False
|
||||||
|
SQLALCHEMY_ECHO = False
|
||||||
|
|
||||||
|
# 配置字典
|
||||||
|
config = {
|
||||||
|
'development': DevelopmentConfig,
|
||||||
|
'production': ProductionConfig,
|
||||||
|
'default': DevelopmentConfig
|
||||||
|
}
|
||||||
117
init_db.py
Normal file
117
init_db.py
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
数据库初始化脚本
|
||||||
|
用于创建数据库表和初始化示例数据
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from app import create_app
|
||||||
|
from models import db, Site, Tag, Admin
|
||||||
|
|
||||||
|
# 设置UTF-8编码输出
|
||||||
|
if sys.platform.startswith('win'):
|
||||||
|
import io
|
||||||
|
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
|
||||||
|
|
||||||
|
def init_database():
|
||||||
|
"""初始化数据库"""
|
||||||
|
app = create_app('development')
|
||||||
|
|
||||||
|
with app.app_context():
|
||||||
|
print("正在创建数据库表...")
|
||||||
|
# 删除所有表(开发环境)
|
||||||
|
db.drop_all()
|
||||||
|
# 创建所有表
|
||||||
|
db.create_all()
|
||||||
|
print("✓ 数据库表创建成功")
|
||||||
|
|
||||||
|
# 创建默认管理员
|
||||||
|
print("\n正在创建默认管理员...")
|
||||||
|
admin = Admin(
|
||||||
|
username='admin',
|
||||||
|
email='admin@example.com',
|
||||||
|
is_active=True
|
||||||
|
)
|
||||||
|
admin.set_password('admin123') # 默认密码
|
||||||
|
db.session.add(admin)
|
||||||
|
print("✓ 默认管理员创建成功")
|
||||||
|
print(" 用户名: admin")
|
||||||
|
print(" 密码: admin123")
|
||||||
|
|
||||||
|
# 创建示例标签
|
||||||
|
print("\n正在创建示例标签...")
|
||||||
|
tags_data = [
|
||||||
|
{'name': 'AI对话', 'slug': 'ai-chat', 'description': 'AI聊天和对话工具', 'icon': 'fas fa-comments', 'sort_order': 100},
|
||||||
|
{'name': '图像生成', 'slug': 'image-gen', 'description': 'AI图像生成和编辑工具', 'icon': 'fas fa-image', 'sort_order': 90},
|
||||||
|
{'name': '视频制作', 'slug': 'video', 'description': 'AI视频生成和编辑工具', 'icon': 'fas fa-video', 'sort_order': 80},
|
||||||
|
{'name': '写作助手', 'slug': 'writing', 'description': 'AI写作和文本生成工具', 'icon': 'fas fa-pen', 'sort_order': 70},
|
||||||
|
{'name': '代码助手', 'slug': 'coding', 'description': 'AI编程和代码生成工具', 'icon': 'fas fa-code', 'sort_order': 60},
|
||||||
|
{'name': '音频处理', 'slug': 'audio', 'description': 'AI音频生成和处理工具', 'icon': 'fas fa-music', 'sort_order': 50},
|
||||||
|
]
|
||||||
|
|
||||||
|
tags = []
|
||||||
|
for tag_data in tags_data:
|
||||||
|
tag = Tag(**tag_data)
|
||||||
|
db.session.add(tag)
|
||||||
|
tags.append(tag)
|
||||||
|
db.session.commit()
|
||||||
|
print(f"✓ 创建了 {len(tags)} 个示例标签")
|
||||||
|
|
||||||
|
# 创建示例网站
|
||||||
|
print("\n正在创建示例网站...")
|
||||||
|
sites_data = [
|
||||||
|
{
|
||||||
|
'name': 'ChatGPT',
|
||||||
|
'url': 'https://chat.openai.com',
|
||||||
|
'slug': 'chatgpt',
|
||||||
|
'short_desc': '最强大的AI对话助手,可以回答问题、写作、编程等',
|
||||||
|
'description': 'ChatGPT是OpenAI开发的大型语言模型,能够进行自然对话、回答问题、协助写作、编程等多种任务。它基于GPT-4架构,拥有强大的理解和生成能力。',
|
||||||
|
'features': '• 自然语言对话\n• 代码编写和调试\n• 文章写作和润色\n• 数据分析\n• 创意头脑风暴',
|
||||||
|
'tags': [tags[0], tags[3], tags[4]],
|
||||||
|
'sort_order': 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'Midjourney',
|
||||||
|
'url': 'https://www.midjourney.com',
|
||||||
|
'slug': 'midjourney',
|
||||||
|
'short_desc': '顶级AI绘画工具,可以根据文字描述生成精美图片',
|
||||||
|
'description': 'Midjourney是一款强大的AI图像生成工具,通过简单的文字描述就能创作出高质量的艺术作品。支持多种艺术风格,广泛应用于设计、插画等领域。',
|
||||||
|
'features': '• 文字转图像\n• 多种艺术风格\n• 高清图片输出\n• 图片变体生成\n• 社区画廊',
|
||||||
|
'tags': [tags[1]],
|
||||||
|
'sort_order': 95
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'GitHub Copilot',
|
||||||
|
'url': 'https://github.com/features/copilot',
|
||||||
|
'slug': 'github-copilot',
|
||||||
|
'short_desc': 'AI编程助手,帮助你更快地编写代码',
|
||||||
|
'description': 'GitHub Copilot是由GitHub和OpenAI联合开发的AI编程助手,可以根据上下文自动建议代码补全,支持多种编程语言。',
|
||||||
|
'features': '• 智能代码补全\n• 多语言支持\n• 函数生成\n• 代码注释生成\n• IDE集成',
|
||||||
|
'tags': [tags[4]],
|
||||||
|
'sort_order': 85
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
for site_data in sites_data:
|
||||||
|
site = Site(**site_data)
|
||||||
|
db.session.add(site)
|
||||||
|
db.session.commit()
|
||||||
|
print(f"✓ 创建了 {len(sites_data)} 个示例网站")
|
||||||
|
|
||||||
|
print("\n" + "="*50)
|
||||||
|
print("数据库初始化完成!")
|
||||||
|
print("="*50)
|
||||||
|
print("\n你可以使用以下命令启动应用:")
|
||||||
|
print(" python app.py")
|
||||||
|
print("\n然后访问:")
|
||||||
|
print(" 前台: http://localhost:5000")
|
||||||
|
print(" 后台: http://localhost:5000/admin")
|
||||||
|
print(" 登录: http://localhost:5000/admin/login")
|
||||||
|
print("\n管理员账号:")
|
||||||
|
print(" 用户名: admin")
|
||||||
|
print(" 密码: admin123")
|
||||||
|
print("\n⚠️ 请在生产环境中修改默认密码!")
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
init_database()
|
||||||
102
models.py
Normal file
102
models.py
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
|
from flask_login import UserMixin
|
||||||
|
from werkzeug.security import generate_password_hash, check_password_hash
|
||||||
|
|
||||||
|
db = SQLAlchemy()
|
||||||
|
|
||||||
|
# 网站和标签的多对多关系表
|
||||||
|
site_tags = db.Table('site_tags',
|
||||||
|
db.Column('site_id', db.Integer, db.ForeignKey('sites.id'), primary_key=True),
|
||||||
|
db.Column('tag_id', db.Integer, db.ForeignKey('tags.id'), primary_key=True)
|
||||||
|
)
|
||||||
|
|
||||||
|
class Site(db.Model):
|
||||||
|
"""网站模型"""
|
||||||
|
__tablename__ = 'sites'
|
||||||
|
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
name = db.Column(db.String(100), nullable=False, comment='网站名称')
|
||||||
|
url = db.Column(db.String(500), nullable=False, comment='网站URL')
|
||||||
|
slug = db.Column(db.String(100), unique=True, nullable=False, comment='URL别名')
|
||||||
|
logo = db.Column(db.String(500), comment='Logo图片路径')
|
||||||
|
short_desc = db.Column(db.String(200), comment='简短描述')
|
||||||
|
description = db.Column(db.Text, comment='详细介绍')
|
||||||
|
features = db.Column(db.Text, comment='主要功能')
|
||||||
|
is_active = db.Column(db.Boolean, default=True, comment='是否启用')
|
||||||
|
view_count = db.Column(db.Integer, default=0, comment='浏览次数')
|
||||||
|
sort_order = db.Column(db.Integer, default=0, comment='排序权重')
|
||||||
|
created_at = db.Column(db.DateTime, default=datetime.now, comment='创建时间')
|
||||||
|
updated_at = db.Column(db.DateTime, default=datetime.now, onupdate=datetime.now, comment='更新时间')
|
||||||
|
|
||||||
|
# 关联标签
|
||||||
|
tags = db.relationship('Tag', secondary=site_tags, lazy='subquery',
|
||||||
|
backref=db.backref('sites', lazy=True))
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f'<Site {self.name}>'
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
"""转换为字典"""
|
||||||
|
return {
|
||||||
|
'id': self.id,
|
||||||
|
'name': self.name,
|
||||||
|
'url': self.url,
|
||||||
|
'slug': self.slug,
|
||||||
|
'logo': self.logo,
|
||||||
|
'short_desc': self.short_desc,
|
||||||
|
'description': self.description,
|
||||||
|
'features': self.features,
|
||||||
|
'is_active': self.is_active,
|
||||||
|
'view_count': self.view_count,
|
||||||
|
'tags': [tag.name for tag in self.tags],
|
||||||
|
'created_at': self.created_at.strftime('%Y-%m-%d %H:%M:%S') if self.created_at else None
|
||||||
|
}
|
||||||
|
|
||||||
|
class Tag(db.Model):
|
||||||
|
"""标签模型"""
|
||||||
|
__tablename__ = 'tags'
|
||||||
|
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
name = db.Column(db.String(50), unique=True, nullable=False, comment='标签名称')
|
||||||
|
slug = db.Column(db.String(50), unique=True, nullable=False, comment='URL别名')
|
||||||
|
description = db.Column(db.String(200), comment='标签描述')
|
||||||
|
icon = db.Column(db.String(100), comment='图标')
|
||||||
|
sort_order = db.Column(db.Integer, default=0, comment='排序权重')
|
||||||
|
created_at = db.Column(db.DateTime, default=datetime.now, comment='创建时间')
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f'<Tag {self.name}>'
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
"""转换为字典"""
|
||||||
|
return {
|
||||||
|
'id': self.id,
|
||||||
|
'name': self.name,
|
||||||
|
'slug': self.slug,
|
||||||
|
'description': self.description,
|
||||||
|
'icon': self.icon
|
||||||
|
}
|
||||||
|
|
||||||
|
class Admin(UserMixin, db.Model):
|
||||||
|
"""管理员模型"""
|
||||||
|
__tablename__ = 'admins'
|
||||||
|
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
username = db.Column(db.String(50), unique=True, nullable=False, comment='用户名')
|
||||||
|
password_hash = db.Column(db.String(255), nullable=False, comment='密码哈希')
|
||||||
|
email = db.Column(db.String(100), comment='邮箱')
|
||||||
|
is_active = db.Column(db.Boolean, default=True, comment='是否启用')
|
||||||
|
created_at = db.Column(db.DateTime, default=datetime.now, comment='创建时间')
|
||||||
|
last_login = db.Column(db.DateTime, comment='最后登录时间')
|
||||||
|
|
||||||
|
def set_password(self, password):
|
||||||
|
"""设置密码"""
|
||||||
|
self.password_hash = generate_password_hash(password)
|
||||||
|
|
||||||
|
def check_password(self, password):
|
||||||
|
"""验证密码"""
|
||||||
|
return check_password_hash(self.password_hash, password)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f'<Admin {self.username}>'
|
||||||
12
requirements.txt
Normal file
12
requirements.txt
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
Flask==3.0.0
|
||||||
|
Flask-SQLAlchemy==3.1.1
|
||||||
|
Flask-Admin==1.6.1
|
||||||
|
Flask-Login==0.6.3
|
||||||
|
pymysql==1.1.0
|
||||||
|
python-dotenv==1.0.0
|
||||||
|
Werkzeug==3.0.1
|
||||||
|
cryptography==41.0.7
|
||||||
|
WTForms==2.3.3
|
||||||
|
requests==2.31.0
|
||||||
|
beautifulsoup4==4.12.2
|
||||||
|
Pillow>=10.2.0
|
||||||
313
static/css/admin-theme.css
Normal file
313
static/css/admin-theme.css
Normal file
@@ -0,0 +1,313 @@
|
|||||||
|
/* ========== Flask-Admin 后台科技感主题 - ZJPB焦提示词 ========== */
|
||||||
|
|
||||||
|
/* 深色主题覆盖 */
|
||||||
|
body.admin-theme {
|
||||||
|
background: #111618 !important;
|
||||||
|
background-image:
|
||||||
|
radial-gradient(at 20% 20%, rgba(37, 192, 244, 0.08) 0px, transparent 50%),
|
||||||
|
radial-gradient(at 80% 80%, rgba(124, 58, 237, 0.08) 0px, transparent 50%);
|
||||||
|
color: #ffffff !important;
|
||||||
|
font-family: 'Space Grotesk', 'Noto Sans', sans-serif !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 导航栏 */
|
||||||
|
.navbar-admin {
|
||||||
|
background: rgba(27, 36, 39, 0.95) !important;
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
border-bottom: 1px solid #283539;
|
||||||
|
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-admin .navbar-brand {
|
||||||
|
background: linear-gradient(to right, #25c0f4, #c084fc);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
background-clip: text;
|
||||||
|
font-weight: 700;
|
||||||
|
font-family: 'Space Grotesk', sans-serif !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 侧边栏 */
|
||||||
|
.nav-sidebar {
|
||||||
|
background: rgba(27, 36, 39, 0.8) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-sidebar .nav-link {
|
||||||
|
color: #9cb2ba !important;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-sidebar .nav-link:hover,
|
||||||
|
.nav-sidebar .nav-link.active {
|
||||||
|
color: #ffffff !important;
|
||||||
|
background: rgba(37, 192, 244, 0.15) !important;
|
||||||
|
border-left: 3px solid #25c0f4;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 卡片和面板 */
|
||||||
|
.card, .panel {
|
||||||
|
background: rgba(27, 36, 39, 0.6) !important;
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
border: 1px solid #283539 !important;
|
||||||
|
border-radius: 12px !important;
|
||||||
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header, .panel-heading {
|
||||||
|
background: rgba(37, 192, 244, 0.08) !important;
|
||||||
|
border-bottom: 1px solid #283539 !important;
|
||||||
|
color: #ffffff !important;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 表格 */
|
||||||
|
.table {
|
||||||
|
color: #ffffff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table thead th {
|
||||||
|
background: rgba(30, 39, 44, 0.8) !important;
|
||||||
|
border-color: #283539 !important;
|
||||||
|
color: #9cb2ba !important;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table tbody tr {
|
||||||
|
background: transparent !important;
|
||||||
|
border-color: #283539 !important;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table tbody tr:hover {
|
||||||
|
background: rgba(30, 39, 44, 0.5) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table td, .table th {
|
||||||
|
border-color: #283539 !important;
|
||||||
|
color: #ffffff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 表单 */
|
||||||
|
.form-control {
|
||||||
|
background: #111618 !important;
|
||||||
|
border: 1px solid #283539 !important;
|
||||||
|
color: #ffffff !important;
|
||||||
|
border-radius: 8px !important;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control:focus {
|
||||||
|
background: rgba(27, 36, 39, 0.8) !important;
|
||||||
|
border-color: #25c0f4 !important;
|
||||||
|
box-shadow: 0 0 0 3px rgba(37, 192, 244, 0.1) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control::placeholder {
|
||||||
|
color: #9cb2ba !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-label, label {
|
||||||
|
color: #9cb2ba !important;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 按钮 */
|
||||||
|
.btn-primary {
|
||||||
|
background: #25c0f4 !important;
|
||||||
|
border: none !important;
|
||||||
|
color: #111618 !important;
|
||||||
|
font-weight: 600;
|
||||||
|
box-shadow: 0 0 20px rgba(37, 192, 244, 0.3);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
background: #1fa8d8 !important;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 0 30px rgba(37, 192, 244, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-info {
|
||||||
|
background: linear-gradient(135deg, #25c0f4 0%, #00f2fe 100%) !important;
|
||||||
|
border: none !important;
|
||||||
|
color: #111618 !important;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary, .btn-default {
|
||||||
|
background: rgba(40, 53, 57, 0.6) !important;
|
||||||
|
border: 1px solid #283539 !important;
|
||||||
|
color: #ffffff !important;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary:hover, .btn-default:hover {
|
||||||
|
background: rgba(52, 66, 71, 0.8) !important;
|
||||||
|
border-color: #4a5a60 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 模态框 */
|
||||||
|
.modal-content {
|
||||||
|
background: rgba(27, 36, 39, 0.95) !important;
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
border: 1px solid #283539 !important;
|
||||||
|
border-radius: 12px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header {
|
||||||
|
border-bottom-color: #283539 !important;
|
||||||
|
background: rgba(37, 192, 244, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-footer {
|
||||||
|
border-top-color: #283539 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 分页 */
|
||||||
|
.pagination .page-link {
|
||||||
|
background: rgba(27, 36, 39, 0.6) !important;
|
||||||
|
border-color: #283539 !important;
|
||||||
|
color: #9cb2ba !important;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination .page-link:hover {
|
||||||
|
background: rgba(37, 192, 244, 0.1) !important;
|
||||||
|
border-color: #25c0f4 !important;
|
||||||
|
color: #ffffff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination .page-item.active .page-link {
|
||||||
|
background: #25c0f4 !important;
|
||||||
|
border-color: #25c0f4 !important;
|
||||||
|
color: #111618 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 警告框 */
|
||||||
|
.alert {
|
||||||
|
background: rgba(27, 36, 39, 0.8) !important;
|
||||||
|
border: 1px solid #283539 !important;
|
||||||
|
color: #ffffff !important;
|
||||||
|
border-radius: 8px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-success {
|
||||||
|
background: rgba(34, 197, 94, 0.1) !important;
|
||||||
|
border-color: rgba(34, 197, 94, 0.3) !important;
|
||||||
|
color: #4ade80 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-danger {
|
||||||
|
background: rgba(239, 68, 68, 0.1) !important;
|
||||||
|
border-color: rgba(239, 68, 68, 0.3) !important;
|
||||||
|
color: #f87171 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-info {
|
||||||
|
background: rgba(37, 192, 244, 0.1) !important;
|
||||||
|
border-color: rgba(37, 192, 244, 0.3) !important;
|
||||||
|
color: #25c0f4 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-warning {
|
||||||
|
background: rgba(251, 191, 36, 0.1) !important;
|
||||||
|
border-color: rgba(251, 191, 36, 0.3) !important;
|
||||||
|
color: #fbbf24 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 链接 */
|
||||||
|
a {
|
||||||
|
color: #25c0f4 !important;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
color: #1fa8d8 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 文本颜色 */
|
||||||
|
.text-muted {
|
||||||
|
color: #9cb2ba !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 输入组 */
|
||||||
|
.input-group-text {
|
||||||
|
background: rgba(27, 36, 39, 0.6) !important;
|
||||||
|
border-color: #283539 !important;
|
||||||
|
color: #9cb2ba !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Select2 下拉框 */
|
||||||
|
.select2-container--bootstrap4 .select2-selection {
|
||||||
|
background: #111618 !important;
|
||||||
|
border-color: #283539 !important;
|
||||||
|
color: #ffffff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-dropdown {
|
||||||
|
background: rgba(27, 36, 39, 0.95) !important;
|
||||||
|
border-color: #283539 !important;
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-results__option {
|
||||||
|
color: #ffffff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-results__option--highlighted {
|
||||||
|
background: rgba(37, 192, 244, 0.2) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 徽章 */
|
||||||
|
.badge-primary {
|
||||||
|
background: #25c0f4 !important;
|
||||||
|
color: #111618 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-secondary {
|
||||||
|
background: #283539 !important;
|
||||||
|
color: #9cb2ba !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 进度条 */
|
||||||
|
.progress {
|
||||||
|
background: rgba(27, 36, 39, 0.6) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-bar {
|
||||||
|
background: #25c0f4 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 额外优化 */
|
||||||
|
.navbar-nav .nav-link {
|
||||||
|
color: #9cb2ba !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-nav .nav-link:hover {
|
||||||
|
color: #ffffff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 自定义滚动条 */
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
background: #111618;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: #283539;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: #3a4b50;
|
||||||
|
}
|
||||||
|
|
||||||
614
static/css/style.css
Normal file
614
static/css/style.css
Normal file
@@ -0,0 +1,614 @@
|
|||||||
|
/* ========== 科技感/未来风主题样式 ========== */
|
||||||
|
|
||||||
|
/* 全局变量 */
|
||||||
|
:root {
|
||||||
|
--primary-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
--secondary-gradient: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
||||||
|
--tech-gradient: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
|
||||||
|
--dark-bg: #0a0e27;
|
||||||
|
--dark-card: rgba(15, 23, 42, 0.8);
|
||||||
|
--glass-bg: rgba(255, 255, 255, 0.05);
|
||||||
|
--glass-border: rgba(255, 255, 255, 0.1);
|
||||||
|
--text-primary: #ffffff;
|
||||||
|
--text-secondary: #a0aec0;
|
||||||
|
--glow-color: #667eea;
|
||||||
|
--success-glow: #00f2fe;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 全局样式 */
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Microsoft YaHei', sans-serif;
|
||||||
|
background: var(--dark-bg);
|
||||||
|
background-image:
|
||||||
|
radial-gradient(at 0% 0%, rgba(102, 126, 234, 0.2) 0px, transparent 50%),
|
||||||
|
radial-gradient(at 100% 0%, rgba(118, 75, 162, 0.2) 0px, transparent 50%),
|
||||||
|
radial-gradient(at 100% 100%, rgba(79, 172, 254, 0.2) 0px, transparent 50%),
|
||||||
|
radial-gradient(at 0% 100%, rgba(0, 242, 254, 0.2) 0px, transparent 50%);
|
||||||
|
background-attachment: fixed;
|
||||||
|
color: var(--text-primary);
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 导航栏样式 */
|
||||||
|
.navbar {
|
||||||
|
background: var(--dark-card) !important;
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
border-bottom: 1px solid var(--glass-border);
|
||||||
|
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-brand {
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
background: var(--primary-gradient);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
background-clip: text;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-brand:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
filter: brightness(1.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-brand i {
|
||||||
|
background: var(--tech-gradient);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
background-clip: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-link {
|
||||||
|
color: var(--text-secondary) !important;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-link:hover {
|
||||||
|
color: var(--text-primary) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-link::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 50%;
|
||||||
|
width: 0;
|
||||||
|
height: 2px;
|
||||||
|
background: var(--tech-gradient);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-link:hover::after {
|
||||||
|
width: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 页面标题 */
|
||||||
|
.page-header {
|
||||||
|
text-align: center;
|
||||||
|
padding: 4rem 0 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-title {
|
||||||
|
font-size: 3.5rem;
|
||||||
|
font-weight: 800;
|
||||||
|
background: var(--primary-gradient);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
background-clip: text;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
text-shadow: 0 0 40px rgba(102, 126, 234, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-subtitle {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: 1.2rem;
|
||||||
|
font-weight: 300;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 标签筛选 */
|
||||||
|
.tag-filter, .tags-filter {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 1rem;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 2rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-item {
|
||||||
|
padding: 0.75rem 1.5rem;
|
||||||
|
background: var(--glass-bg);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
border: 1px solid var(--glass-border);
|
||||||
|
border-radius: 50px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-item::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: -100%;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: var(--tech-gradient);
|
||||||
|
transition: all 0.4s ease;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-item:hover {
|
||||||
|
color: var(--text-primary);
|
||||||
|
border-color: transparent;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 8px 25px rgba(79, 172, 254, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-item:hover::before {
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-item.active {
|
||||||
|
background: var(--primary-gradient);
|
||||||
|
color: var(--text-primary);
|
||||||
|
border-color: transparent;
|
||||||
|
box-shadow: 0 8px 25px rgba(102, 126, 234, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-item i {
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 网站卡片 */
|
||||||
|
.site-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
||||||
|
gap: 2rem;
|
||||||
|
padding: 2rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-card {
|
||||||
|
background: var(--glass-bg);
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
border: 1px solid var(--glass-border);
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 2rem;
|
||||||
|
transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-card::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: var(--primary-gradient);
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.4s ease;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-card:hover {
|
||||||
|
transform: translateY(-10px) scale(1.02);
|
||||||
|
border-color: rgba(102, 126, 234, 0.5);
|
||||||
|
box-shadow:
|
||||||
|
0 20px 60px rgba(102, 126, 234, 0.3),
|
||||||
|
0 0 40px rgba(79, 172, 254, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-card:hover::before {
|
||||||
|
opacity: 0.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-card > * {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-logo {
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
object-fit: contain;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
border-radius: 15px;
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
padding: 10px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-card:hover .site-logo {
|
||||||
|
transform: scale(1.1) rotate(5deg);
|
||||||
|
filter: drop-shadow(0 0 20px rgba(79, 172, 254, 0.5));
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-logo-placeholder {
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
background: var(--tech-gradient);
|
||||||
|
border-radius: 15px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: white;
|
||||||
|
font-size: 32px;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-name, .card-title {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--text-primary);
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-desc, .card-text {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: 0.95rem;
|
||||||
|
line-height: 1.6;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-tags {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.5rem;
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-tags .badge {
|
||||||
|
padding: 0.4rem 0.8rem;
|
||||||
|
background: rgba(79, 172, 254, 0.1);
|
||||||
|
border: 1px solid rgba(79, 172, 254, 0.3);
|
||||||
|
border-radius: 20px;
|
||||||
|
color: #4facfe;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-meta {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
padding-top: 1rem;
|
||||||
|
border-top: 1px solid var(--glass-border);
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-meta i {
|
||||||
|
margin-right: 0.3rem;
|
||||||
|
color: #4facfe;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 详情页样式 */
|
||||||
|
.detail-container {
|
||||||
|
max-width: 900px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 3rem 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-header {
|
||||||
|
background: var(--glass-bg);
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
border: 1px solid var(--glass-border);
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 3rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-logo-large {
|
||||||
|
width: 120px !important;
|
||||||
|
height: 120px !important;
|
||||||
|
margin: 0 auto 2rem;
|
||||||
|
padding: 15px;
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
border-radius: 25px;
|
||||||
|
border: 2px solid var(--glass-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-title {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
font-weight: 800;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
background: var(--primary-gradient);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
background-clip: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-url {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 1rem 2rem;
|
||||||
|
background: var(--tech-gradient);
|
||||||
|
color: white;
|
||||||
|
text-decoration: none;
|
||||||
|
border-radius: 50px;
|
||||||
|
font-weight: 600;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
box-shadow: 0 8px 25px rgba(79, 172, 254, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-url:hover {
|
||||||
|
transform: translateY(-3px);
|
||||||
|
box-shadow: 0 12px 35px rgba(79, 172, 254, 0.5);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-section, .site-description, .site-features {
|
||||||
|
background: var(--glass-bg);
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
border: 1px solid var(--glass-border);
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 2.5rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-section h3 {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
color: var(--text-primary);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-section h3 i {
|
||||||
|
margin-right: 0.75rem;
|
||||||
|
background: var(--tech-gradient);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
background-clip: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-section p,
|
||||||
|
.detail-section ul,
|
||||||
|
.site-description p,
|
||||||
|
.site-description ul,
|
||||||
|
.site-features ul {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
line-height: 1.8;
|
||||||
|
font-size: 1.05rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-section ul,
|
||||||
|
.site-description ul,
|
||||||
|
.site-features ul {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-section li,
|
||||||
|
.site-description li,
|
||||||
|
.site-features li {
|
||||||
|
padding: 0.75rem 0;
|
||||||
|
padding-left: 2rem;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-section li::before,
|
||||||
|
.site-description li::before,
|
||||||
|
.site-features li::before {
|
||||||
|
content: '▸';
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
color: #4facfe;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 页脚 */
|
||||||
|
footer {
|
||||||
|
background: var(--dark-card) !important;
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
border-top: 1px solid var(--glass-border);
|
||||||
|
margin-top: 5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 响应式设计 */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.page-title {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-filter, .tags-filter {
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-item {
|
||||||
|
padding: 0.6rem 1.2rem;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-logo-large {
|
||||||
|
width: 80px !important;
|
||||||
|
height: 80px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 按钮样式 */
|
||||||
|
.btn-primary {
|
||||||
|
background: var(--primary-gradient) !important;
|
||||||
|
border: none !important;
|
||||||
|
padding: 0.75rem 2rem;
|
||||||
|
border-radius: 50px;
|
||||||
|
font-weight: 600;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
box-shadow: 0 8px 25px rgba(102, 126, 234, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
transform: translateY(-3px);
|
||||||
|
box-shadow: 0 12px 35px rgba(102, 126, 234, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary {
|
||||||
|
background: var(--glass-bg) !important;
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
border: 1px solid var(--glass-border) !important;
|
||||||
|
color: var(--text-primary) !important;
|
||||||
|
padding: 0.75rem 2rem;
|
||||||
|
border-radius: 50px;
|
||||||
|
font-weight: 600;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.1) !important;
|
||||||
|
border-color: rgba(255, 255, 255, 0.3) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
border-radius: 50px;
|
||||||
|
padding: 0.75rem 1.5rem;
|
||||||
|
font-weight: 600;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 表单样式 */
|
||||||
|
.form-control {
|
||||||
|
background: var(--glass-bg) !important;
|
||||||
|
border: 1px solid var(--glass-border) !important;
|
||||||
|
color: var(--text-primary) !important;
|
||||||
|
border-radius: 15px;
|
||||||
|
padding: 0.75rem 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control:focus {
|
||||||
|
background: rgba(255, 255, 255, 0.1) !important;
|
||||||
|
border-color: #4facfe !important;
|
||||||
|
box-shadow: 0 0 20px rgba(79, 172, 254, 0.3) !important;
|
||||||
|
color: var(--text-primary) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control::placeholder {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-label {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 卡片样式 */
|
||||||
|
.card {
|
||||||
|
background: var(--glass-bg) !important;
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
border: 1px solid var(--glass-border) !important;
|
||||||
|
border-radius: 20px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-body {
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 警告框样式 */
|
||||||
|
.alert {
|
||||||
|
background: var(--glass-bg) !important;
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
border: 1px solid var(--glass-border) !important;
|
||||||
|
border-radius: 15px !important;
|
||||||
|
color: var(--text-primary) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-danger {
|
||||||
|
border-color: rgba(245, 87, 108, 0.5) !important;
|
||||||
|
background: rgba(245, 87, 108, 0.1) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-success {
|
||||||
|
border-color: rgba(0, 242, 254, 0.5) !important;
|
||||||
|
background: rgba(0, 242, 254, 0.1) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 空状态样式 */
|
||||||
|
.empty-state {
|
||||||
|
padding: 60px 20px;
|
||||||
|
text-align: center;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state i {
|
||||||
|
font-size: 4rem;
|
||||||
|
background: var(--primary-gradient);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
background-clip: text;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 滚动条样式 */
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
background: var(--dark-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: var(--glass-bg);
|
||||||
|
border-radius: 5px;
|
||||||
|
border: 1px solid var(--glass-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 加载动画 */
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(20px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-card {
|
||||||
|
animation: fadeIn 0.5s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes glow {
|
||||||
|
0%, 100% {
|
||||||
|
box-shadow: 0 0 20px rgba(102, 126, 234, 0.5);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
box-shadow: 0 0 40px rgba(79, 172, 254, 0.8);
|
||||||
|
}
|
||||||
|
}
|
||||||
121
static/js/main.js
Normal file
121
static/js/main.js
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
// 主要JavaScript功能
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
// 初始化所有功能
|
||||||
|
initCardAnimations();
|
||||||
|
initExternalLinks();
|
||||||
|
initTooltips();
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 卡片动画效果
|
||||||
|
*/
|
||||||
|
function initCardAnimations() {
|
||||||
|
const cards = document.querySelectorAll('.site-card');
|
||||||
|
|
||||||
|
cards.forEach((card, index) => {
|
||||||
|
// 添加延迟动画效果
|
||||||
|
card.style.animationDelay = `${index * 0.05}s`;
|
||||||
|
|
||||||
|
// 点击卡片查看详情
|
||||||
|
card.addEventListener('click', function(e) {
|
||||||
|
// 如果点击的是按钮,不触发卡片点击
|
||||||
|
if (e.target.tagName === 'A' || e.target.closest('a')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 找到"查看详情"按钮并触发点击
|
||||||
|
const detailBtn = card.querySelector('a[href*="site_detail"]');
|
||||||
|
if (detailBtn) {
|
||||||
|
window.location.href = detailBtn.href;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 外部链接处理
|
||||||
|
*/
|
||||||
|
function initExternalLinks() {
|
||||||
|
const externalLinks = document.querySelectorAll('a[target="_blank"]');
|
||||||
|
|
||||||
|
externalLinks.forEach(link => {
|
||||||
|
// 添加安全属性
|
||||||
|
link.setAttribute('rel', 'noopener noreferrer');
|
||||||
|
|
||||||
|
// 添加点击统计(可选)
|
||||||
|
link.addEventListener('click', function() {
|
||||||
|
console.log('External link clicked:', this.href);
|
||||||
|
// 这里可以添加统计代码
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化Bootstrap提示框
|
||||||
|
*/
|
||||||
|
function initTooltips() {
|
||||||
|
const tooltipTriggerList = [].slice.call(
|
||||||
|
document.querySelectorAll('[data-bs-toggle="tooltip"]')
|
||||||
|
);
|
||||||
|
tooltipTriggerList.map(function(tooltipTriggerEl) {
|
||||||
|
return new bootstrap.Tooltip(tooltipTriggerEl);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 平滑滚动到顶部
|
||||||
|
*/
|
||||||
|
function scrollToTop() {
|
||||||
|
window.scrollTo({
|
||||||
|
top: 0,
|
||||||
|
behavior: 'smooth'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示加载动画
|
||||||
|
*/
|
||||||
|
function showLoading() {
|
||||||
|
const loader = document.createElement('div');
|
||||||
|
loader.id = 'loading';
|
||||||
|
loader.className = 'loading-overlay';
|
||||||
|
loader.innerHTML = '<div class="spinner-border text-primary" role="status"></div>';
|
||||||
|
document.body.appendChild(loader);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 隐藏加载动画
|
||||||
|
*/
|
||||||
|
function hideLoading() {
|
||||||
|
const loader = document.getElementById('loading');
|
||||||
|
if (loader) {
|
||||||
|
loader.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示通知消息
|
||||||
|
*/
|
||||||
|
function showNotification(message, type = 'info') {
|
||||||
|
const alert = document.createElement('div');
|
||||||
|
alert.className = `alert alert-${type} alert-dismissible fade show position-fixed top-0 start-50 translate-middle-x mt-3`;
|
||||||
|
alert.style.zIndex = '9999';
|
||||||
|
alert.innerHTML = `
|
||||||
|
${message}
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||||
|
`;
|
||||||
|
|
||||||
|
document.body.appendChild(alert);
|
||||||
|
|
||||||
|
// 3秒后自动关闭
|
||||||
|
setTimeout(() => {
|
||||||
|
alert.remove();
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出函数供全局使用
|
||||||
|
window.scrollToTop = scrollToTop;
|
||||||
|
window.showLoading = showLoading;
|
||||||
|
window.hideLoading = hideLoading;
|
||||||
|
window.showNotification = showNotification;
|
||||||
0
static/uploads/.gitkeep
Normal file
0
static/uploads/.gitkeep
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
|
||||||
|
<html class="dark" lang="en"><head>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
||||||
|
<title>Admin Login - AI Discovery</title>
|
||||||
|
<!-- Fonts -->
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&family=Noto+Sans:wght@300;400;500;600;700&display=swap" rel="stylesheet"/>
|
||||||
|
<!-- Material Symbols -->
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
||||||
|
<!-- Tailwind CSS -->
|
||||||
|
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
||||||
|
<script id="tailwind-config">
|
||||||
|
tailwind.config = {
|
||||||
|
darkMode: "class",
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
colors: {
|
||||||
|
"primary": "#25c0f4",
|
||||||
|
"background-light": "#f5f8f8",
|
||||||
|
"background-dark": "#101e22",
|
||||||
|
"surface-dark": "#162125",
|
||||||
|
"input-bg": "#1b2427",
|
||||||
|
"input-border": "#3b4e54",
|
||||||
|
"text-muted": "#9cb2ba",
|
||||||
|
},
|
||||||
|
fontFamily: {
|
||||||
|
"display": ["Space Grotesk", "sans-serif"],
|
||||||
|
"body": ["Noto Sans", "sans-serif"],
|
||||||
|
},
|
||||||
|
borderRadius: {"DEFAULT": "0.25rem", "lg": "0.5rem", "xl": "0.75rem", "full": "9999px"},
|
||||||
|
backgroundImage: {
|
||||||
|
'tech-grid': "radial-gradient(circle at center, rgba(37, 192, 244, 0.05) 0%, transparent 70%), linear-gradient(rgba(37, 192, 244, 0.03) 1px, transparent 1px), linear-gradient(90deg, rgba(37, 192, 244, 0.03) 1px, transparent 1px)",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
.glass-panel {
|
||||||
|
background: rgba(22, 33, 37, 0.7);
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
|
-webkit-backdrop-filter: blur(12px);
|
||||||
|
}
|
||||||
|
.tech-bg-size {
|
||||||
|
background-size: 100% 100%, 40px 40px, 40px 40px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body class="bg-background-light dark:bg-background-dark font-display text-slate-900 dark:text-white min-h-screen flex flex-col items-center justify-center relative overflow-hidden selection:bg-primary/30">
|
||||||
|
<!-- Ambient Background -->
|
||||||
|
<div class="absolute inset-0 z-0 bg-tech-grid tech-bg-size pointer-events-none" data-alt="Abstract subtle grid pattern background representing technology"></div>
|
||||||
|
<div class="absolute top-[-20%] right-[-10%] w-[600px] h-[600px] bg-primary/10 rounded-full blur-[120px] pointer-events-none"></div>
|
||||||
|
<div class="absolute bottom-[-10%] left-[-10%] w-[500px] h-[500px] bg-purple-600/10 rounded-full blur-[100px] pointer-events-none"></div>
|
||||||
|
<!-- Main Content Wrapper -->
|
||||||
|
<div class="w-full max-w-[480px] px-4 z-10 flex flex-col gap-6">
|
||||||
|
<!-- Back Navigation -->
|
||||||
|
<a class="group flex items-center gap-2 text-text-muted hover:text-white transition-colors w-fit" href="#">
|
||||||
|
<div class="flex items-center justify-center w-8 h-8 rounded-full border border-input-border bg-surface-dark group-hover:border-primary/50 transition-colors">
|
||||||
|
<span class="material-symbols-outlined text-sm">arrow_back</span>
|
||||||
|
</div>
|
||||||
|
<span class="text-sm font-medium">Back to Home</span>
|
||||||
|
</a>
|
||||||
|
<!-- Login Card -->
|
||||||
|
<div class="glass-panel border border-white/5 dark:border-input-border/50 rounded-xl shadow-2xl p-8 md:p-10 relative overflow-hidden">
|
||||||
|
<!-- Decorative Accent -->
|
||||||
|
<div class="absolute top-0 left-0 w-full h-[2px] bg-gradient-to-r from-transparent via-primary to-transparent opacity-70"></div>
|
||||||
|
<!-- Header -->
|
||||||
|
<div class="flex flex-col gap-2 mb-8">
|
||||||
|
<div class="flex items-center gap-3 mb-2">
|
||||||
|
<div class="p-2 rounded-lg bg-primary/10 text-primary">
|
||||||
|
<span class="material-symbols-outlined text-2xl">shield_person</span>
|
||||||
|
</div>
|
||||||
|
<span class="text-xs font-bold tracking-widest uppercase text-primary/80">System Access</span>
|
||||||
|
</div>
|
||||||
|
<h1 class="text-3xl font-black tracking-tight text-white leading-tight">管理员登录</h1>
|
||||||
|
<p class="text-text-muted text-base font-normal">Enter your credentials to access the AI Control Panel.</p>
|
||||||
|
</div>
|
||||||
|
<!-- Error Message Area (Static Example) -->
|
||||||
|
<!-- <div class="mb-6 p-3 rounded-lg bg-red-500/10 border border-red-500/20 flex items-start gap-3">
|
||||||
|
<span class="material-symbols-outlined text-red-400 text-sm mt-0.5">error</span>
|
||||||
|
<p class="text-red-400 text-sm">Invalid credentials provided.</p>
|
||||||
|
</div> -->
|
||||||
|
<!-- Form -->
|
||||||
|
<form class="flex flex-col gap-5">
|
||||||
|
<!-- Username Field -->
|
||||||
|
<div class="space-y-2">
|
||||||
|
<label class="text-white text-sm font-medium leading-normal" for="username">Username</label>
|
||||||
|
<div class="relative group">
|
||||||
|
<input class="form-input flex w-full h-14 pl-12 pr-4 rounded-lg text-white focus:outline-0 focus:ring-0 border border-input-border bg-input-bg focus:border-primary placeholder:text-text-muted text-base font-normal leading-normal transition-colors" id="username" placeholder="Enter your username or email" type="text"/>
|
||||||
|
<div class="absolute left-4 top-1/2 -translate-y-1/2 text-text-muted group-focus-within:text-primary transition-colors flex items-center justify-center pointer-events-none">
|
||||||
|
<span class="material-symbols-outlined text-[20px]">person</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Password Field -->
|
||||||
|
<div class="space-y-2">
|
||||||
|
<div class="flex justify-between items-end">
|
||||||
|
<label class="text-white text-sm font-medium leading-normal" for="password">Password</label>
|
||||||
|
<a class="text-xs text-primary hover:text-white transition-colors mb-1" href="#">Forgot password?</a>
|
||||||
|
</div>
|
||||||
|
<div class="flex w-full items-stretch rounded-lg group relative">
|
||||||
|
<input class="form-input flex w-full h-14 pl-12 pr-12 rounded-lg text-white focus:outline-0 focus:ring-0 border border-input-border bg-input-bg focus:border-primary placeholder:text-text-muted text-base font-normal leading-normal transition-colors z-10" id="password" placeholder="Enter your password" type="password"/>
|
||||||
|
<div class="absolute left-4 top-1/2 -translate-y-1/2 text-text-muted group-focus-within:text-primary transition-colors flex items-center justify-center pointer-events-none z-20">
|
||||||
|
<span class="material-symbols-outlined text-[20px]">lock</span>
|
||||||
|
</div>
|
||||||
|
<button class="absolute right-0 top-0 h-full px-4 text-text-muted hover:text-white transition-colors flex items-center justify-center z-20 focus:outline-none" type="button">
|
||||||
|
<span class="material-symbols-outlined text-[20px]">visibility_off</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Login Button -->
|
||||||
|
<button class="mt-4 flex w-full h-12 cursor-pointer items-center justify-center overflow-hidden rounded-lg bg-primary text-background-dark text-base font-bold leading-normal tracking-[0.015em] hover:bg-primary/90 transition-all active:scale-[0.98] shadow-[0_0_20px_rgba(37,192,244,0.3)] hover:shadow-[0_0_30px_rgba(37,192,244,0.5)]" type="button">
|
||||||
|
<span class="truncate">Login</span>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
<!-- Footer Meta -->
|
||||||
|
<div class="mt-8 flex justify-center gap-6 border-t border-input-border/30 pt-6">
|
||||||
|
<p class="text-text-muted text-xs text-center">
|
||||||
|
Protected by reCAPTCHA. <a class="underline hover:text-primary" href="#">Privacy</a> & <a class="underline hover:text-primary" href="#">Terms</a>.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body></html>
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 518 KiB |
@@ -0,0 +1,394 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
|
||||||
|
<html class="dark" lang="en"><head>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
||||||
|
<title>Admin Management Interface</title>
|
||||||
|
<!-- Fonts -->
|
||||||
|
<link href="https://fonts.googleapis.com" rel="preconnect"/>
|
||||||
|
<link crossorigin="" href="https://fonts.gstatic.com" rel="preconnect"/>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;700&family=Noto+Sans:wght@400;500;700&display=swap" rel="stylesheet"/>
|
||||||
|
<!-- Material Symbols -->
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
||||||
|
<!-- Tailwind CSS -->
|
||||||
|
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
||||||
|
<!-- Theme Configuration -->
|
||||||
|
<script id="tailwind-config">
|
||||||
|
tailwind.config = {
|
||||||
|
darkMode: "class",
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
colors: {
|
||||||
|
"primary": "#25c0f4",
|
||||||
|
"primary-hover": "#0ea5e9",
|
||||||
|
"background-light": "#f5f8f8",
|
||||||
|
"background-dark": "#111618", /* Matched to snippets */
|
||||||
|
"surface-dark": "#192226",
|
||||||
|
"surface-highlight": "#283539",
|
||||||
|
"text-secondary": "#9cb2ba",
|
||||||
|
},
|
||||||
|
fontFamily: {
|
||||||
|
"display": ["Space Grotesk", "sans-serif"],
|
||||||
|
"body": ["Noto Sans", "sans-serif"],
|
||||||
|
},
|
||||||
|
borderRadius: {"DEFAULT": "0.25rem", "lg": "0.5rem", "xl": "0.75rem", "2xl": "1rem", "full": "9999px"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: "Noto Sans", sans-serif;
|
||||||
|
}
|
||||||
|
h1, h2, h3, h4, h5, h6, .font-display {
|
||||||
|
font-family: "Space Grotesk", sans-serif;
|
||||||
|
}
|
||||||
|
/* Custom Scrollbar for dark theme */
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
}
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
background: #111618;
|
||||||
|
}
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: #283539;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: #3a4b50;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body class="bg-background-light dark:bg-background-dark text-slate-900 dark:text-white overflow-hidden flex h-screen w-full">
|
||||||
|
<!-- Sidebar -->
|
||||||
|
<aside class="flex flex-col w-64 h-full border-r border-[#283539] bg-background-dark flex-shrink-0 transition-all duration-300">
|
||||||
|
<div class="p-6 flex items-center gap-3">
|
||||||
|
<div class="size-8 text-primary">
|
||||||
|
<svg fill="none" viewbox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path clip-rule="evenodd" d="M24 18.4228L42 11.475V34.3663C42 34.7796 41.7457 35.1504 41.3601 35.2992L24 42V18.4228Z" fill="currentColor" fill-rule="evenodd"></path>
|
||||||
|
<path clip-rule="evenodd" d="M24 8.18819L33.4123 11.574L24 15.2071L14.5877 11.574L24 8.18819ZM9 15.8487L21 20.4805V37.6263L9 32.9945V15.8487ZM27 37.6263V20.4805L39 15.8487V32.9945L27 37.6263ZM25.354 2.29885C24.4788 1.98402 23.5212 1.98402 22.646 2.29885L4.98454 8.65208C3.7939 9.08038 3 10.2097 3 11.475V34.3663C3 36.0196 4.01719 37.5026 5.55962 38.098L22.9197 44.7987C23.6149 45.0671 24.3851 45.0671 25.0803 44.7987L42.4404 38.098C43.9828 37.5026 45 36.0196 45 34.3663V11.475C45 10.2097 44.2061 9.08038 43.0155 8.65208L25.354 2.29885Z" fill="currentColor" fill-rule="evenodd"></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h1 class="text-white text-xl font-bold tracking-tight">AI Discovery</h1>
|
||||||
|
</div>
|
||||||
|
<nav class="flex-1 px-4 flex flex-col gap-2 mt-4">
|
||||||
|
<div class="px-3 py-2">
|
||||||
|
<p class="text-text-secondary text-xs font-bold uppercase tracking-wider mb-2">Main Menu</p>
|
||||||
|
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg bg-surface-highlight text-white group transition-colors" href="#">
|
||||||
|
<span class="material-symbols-outlined text-primary group-hover:text-white transition-colors">language</span>
|
||||||
|
<span class="text-sm font-medium">Website Management</span>
|
||||||
|
</a>
|
||||||
|
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-secondary hover:bg-surface-highlight hover:text-white transition-colors mt-1" href="#">
|
||||||
|
<span class="material-symbols-outlined">label</span>
|
||||||
|
<span class="text-sm font-medium">Tag Management</span>
|
||||||
|
</a>
|
||||||
|
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-secondary hover:bg-surface-highlight hover:text-white transition-colors mt-1" href="#">
|
||||||
|
<span class="material-symbols-outlined">group</span>
|
||||||
|
<span class="text-sm font-medium">Administrators</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="px-3 py-2 mt-auto">
|
||||||
|
<p class="text-text-secondary text-xs font-bold uppercase tracking-wider mb-2">System</p>
|
||||||
|
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-secondary hover:bg-surface-highlight hover:text-white transition-colors" href="#">
|
||||||
|
<span class="material-symbols-outlined">settings</span>
|
||||||
|
<span class="text-sm font-medium">Settings</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<div class="p-4 border-t border-[#283539]">
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<div class="bg-center bg-no-repeat bg-cover rounded-full size-10 border border-[#283539]" data-alt="User profile picture showing a minimal avatar" style='background-image: url("https://lh3.googleusercontent.com/aida-public/AB6AXuDlD4gGno1M8YZQOQ6QyLdUZtaU7klHxTjeH2Kf3aH39GkQ56ld-iSPZ6RCbkxzCeXJzMq6_29atBkPks_wP-Q7pvpyNAAZW2FJKxOFTSrcQZp2O30dVhn-DPYELqG1fdqM-hPfluduBN-I0k_Q8cJrJ3LKVUUgnxsE8bWD4CSo8CoYvXmtK03j86TehszyrfWfKpoBcu8XaHpjh10EGoDHFYeSJ6AKJQDsp_0iz5LC4bOXvyg0_Nef5mQOBtQ9zKuKSbCYhRuu7Wyo");'></div>
|
||||||
|
<div class="flex flex-col overflow-hidden">
|
||||||
|
<h2 class="text-white text-sm font-medium truncate">Admin User</h2>
|
||||||
|
<p class="text-text-secondary text-xs truncate">admin@ai-discovery.com</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
<!-- Main Content -->
|
||||||
|
<main class="flex-1 flex flex-col h-full overflow-hidden bg-background-dark relative">
|
||||||
|
<!-- Top Header -->
|
||||||
|
<header class="h-16 border-b border-[#283539] bg-background-dark/95 backdrop-blur px-8 flex items-center justify-between shrink-0 z-20">
|
||||||
|
<!-- Breadcrumbs -->
|
||||||
|
<div class="flex items-center gap-2 text-sm">
|
||||||
|
<a class="text-text-secondary hover:text-white transition-colors" href="#">Dashboard</a>
|
||||||
|
<span class="text-text-secondary">/</span>
|
||||||
|
<span class="text-white font-medium">Website Management</span>
|
||||||
|
</div>
|
||||||
|
<!-- Global Actions -->
|
||||||
|
<div class="flex items-center gap-6">
|
||||||
|
<!-- Search -->
|
||||||
|
<div class="relative w-64 hidden md:block">
|
||||||
|
<span class="absolute inset-y-0 left-0 flex items-center pl-3 text-text-secondary">
|
||||||
|
<span class="material-symbols-outlined text-[20px]">search</span>
|
||||||
|
</span>
|
||||||
|
<input class="w-full bg-surface-highlight border-none rounded-lg py-2 pl-10 pr-4 text-sm text-white placeholder-text-secondary focus:ring-1 focus:ring-primary focus:bg-[#1f292d] transition-all" placeholder="Global search..." type="text"/>
|
||||||
|
</div>
|
||||||
|
<!-- Notifications -->
|
||||||
|
<button class="relative text-text-secondary hover:text-white transition-colors">
|
||||||
|
<span class="material-symbols-outlined">notifications</span>
|
||||||
|
<span class="absolute top-0 right-0 size-2 bg-primary rounded-full"></span>
|
||||||
|
</button>
|
||||||
|
<!-- Logout -->
|
||||||
|
<button class="text-text-secondary hover:text-white transition-colors">
|
||||||
|
<span class="material-symbols-outlined">logout</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<!-- Scrollable Content Area -->
|
||||||
|
<div class="flex-1 overflow-y-auto overflow-x-hidden p-8 scroll-smooth">
|
||||||
|
<div class="max-w-[1400px] mx-auto flex flex-col gap-8 pb-10">
|
||||||
|
<!-- Page Title & Toolbar -->
|
||||||
|
<div class="flex flex-col md:flex-row md:items-end justify-between gap-4">
|
||||||
|
<div>
|
||||||
|
<h1 class="text-3xl md:text-4xl font-bold text-white mb-2">Website Management</h1>
|
||||||
|
<p class="text-text-secondary text-base">Manage and curate AI tools for the discovery platform.</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-3">
|
||||||
|
<button class="flex items-center gap-2 h-10 px-4 rounded-lg bg-surface-highlight hover:bg-[#344247] text-white text-sm font-medium transition-colors border border-transparent hover:border-[#4a5a60]">
|
||||||
|
<span class="material-symbols-outlined text-[20px]">filter_list</span>
|
||||||
|
<span>Filter</span>
|
||||||
|
</button>
|
||||||
|
<button class="flex items-center gap-2 h-10 px-4 rounded-lg bg-surface-highlight hover:bg-[#344247] text-white text-sm font-medium transition-colors border border-transparent hover:border-[#4a5a60]">
|
||||||
|
<span class="material-symbols-outlined text-[20px]">download</span>
|
||||||
|
<span>Export</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Content Grid: Table & Quick Add Form -->
|
||||||
|
<div class="grid grid-cols-1 xl:grid-cols-3 gap-8 items-start">
|
||||||
|
<!-- Left Column: Data Table -->
|
||||||
|
<div class="xl:col-span-2 flex flex-col gap-4">
|
||||||
|
<!-- Table Toolbar -->
|
||||||
|
<div class="bg-surface-dark border border-[#283539] rounded-t-xl p-4 flex flex-col sm:flex-row justify-between items-center gap-4">
|
||||||
|
<div class="relative w-full sm:w-72">
|
||||||
|
<span class="absolute inset-y-0 left-0 flex items-center pl-3 text-text-secondary">
|
||||||
|
<span class="material-symbols-outlined text-[20px]">search</span>
|
||||||
|
</span>
|
||||||
|
<input class="w-full bg-[#111618] border border-[#283539] rounded-lg py-2 pl-10 pr-4 text-sm text-white placeholder-text-secondary focus:ring-1 focus:ring-primary" placeholder="Search websites..." type="text"/>
|
||||||
|
</div>
|
||||||
|
<div class="text-sm text-text-secondary">
|
||||||
|
Showing <span class="text-white font-medium">1-10</span> of <span class="text-white font-medium">128</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Table -->
|
||||||
|
<div class="bg-surface-dark border border-[#283539] rounded-b-xl overflow-hidden shadow-xl">
|
||||||
|
<div class="overflow-x-auto">
|
||||||
|
<table class="w-full text-left border-collapse">
|
||||||
|
<thead>
|
||||||
|
<tr class="bg-[#1e272c] border-b border-[#283539]">
|
||||||
|
<th class="p-4 text-xs font-semibold uppercase tracking-wider text-text-secondary w-10">
|
||||||
|
<input class="rounded bg-[#111618] border-[#283539] text-primary focus:ring-offset-[#111618]" type="checkbox"/>
|
||||||
|
</th>
|
||||||
|
<th class="p-4 text-xs font-semibold uppercase tracking-wider text-text-secondary">Website / Tool</th>
|
||||||
|
<th class="p-4 text-xs font-semibold uppercase tracking-wider text-text-secondary">Tags</th>
|
||||||
|
<th class="p-4 text-xs font-semibold uppercase tracking-wider text-text-secondary">Status</th>
|
||||||
|
<th class="p-4 text-xs font-semibold uppercase tracking-wider text-text-secondary text-right">Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="divide-y divide-[#283539]">
|
||||||
|
<!-- Row 1 -->
|
||||||
|
<tr class="group hover:bg-[#1e272c] transition-colors">
|
||||||
|
<td class="p-4">
|
||||||
|
<input class="rounded bg-[#111618] border-[#283539] text-primary focus:ring-offset-[#111618]" type="checkbox"/>
|
||||||
|
</td>
|
||||||
|
<td class="p-4">
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<div class="size-10 rounded-lg bg-cover bg-center shrink-0 border border-[#283539]" data-alt="Icon of Chat AI Tool" style='background-image: url("https://lh3.googleusercontent.com/aida-public/AB6AXuB_TTlnbkP1ygA1GNesXOhx_H9yup1gOKwumL9Qnh8VcPEUlwDRA89LRKhO8tyDtOtvtfrGh7CRSnP5xMOyb_GvJgQ-FQToY20cgHuTpVeI97COVlA4e2wOp04SH5ogk98S2SFF4jtSoS7RldXDdaafrysnsulA7_euKFIDf42NTPk_YwR4aglKFOeiQCQT-ITD6hCZjE8kqEfam8E6onBbJ14KZt2sE8gn0VP-pzzgAgPlr3htq5j6T26Tijk8V7SIFQGlB20GjSsb");'></div>
|
||||||
|
<div>
|
||||||
|
<h3 class="text-sm font-bold text-white group-hover:text-primary transition-colors">ChatGPT</h3>
|
||||||
|
<p class="text-xs text-text-secondary truncate max-w-[200px]">openai.com/chatgpt</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="p-4">
|
||||||
|
<div class="flex flex-wrap gap-1">
|
||||||
|
<span class="px-2 py-0.5 rounded text-[10px] font-medium bg-[#283539] text-white border border-[#3a4b50]">LLM</span>
|
||||||
|
<span class="px-2 py-0.5 rounded text-[10px] font-medium bg-[#283539] text-white border border-[#3a4b50]">Chatbot</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="p-4">
|
||||||
|
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-green-500/10 text-green-400 border border-green-500/20">
|
||||||
|
Active
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td class="p-4 text-right">
|
||||||
|
<div class="flex items-center justify-end gap-2 opacity-0 group-hover:opacity-100 transition-opacity">
|
||||||
|
<button class="p-1.5 rounded-md hover:bg-primary/20 hover:text-primary text-text-secondary transition-colors">
|
||||||
|
<span class="material-symbols-outlined text-[18px]">edit</span>
|
||||||
|
</button>
|
||||||
|
<button class="p-1.5 rounded-md hover:bg-red-500/20 hover:text-red-400 text-text-secondary transition-colors">
|
||||||
|
<span class="material-symbols-outlined text-[18px]">delete</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<!-- Row 2 -->
|
||||||
|
<tr class="group hover:bg-[#1e272c] transition-colors">
|
||||||
|
<td class="p-4">
|
||||||
|
<input class="rounded bg-[#111618] border-[#283539] text-primary focus:ring-offset-[#111618]" type="checkbox"/>
|
||||||
|
</td>
|
||||||
|
<td class="p-4">
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<div class="size-10 rounded-lg bg-cover bg-center shrink-0 border border-[#283539]" data-alt="Icon of Midjourney AI" style='background-image: url("https://lh3.googleusercontent.com/aida-public/AB6AXuA-IWUA_5FInPrqeTtZDcb85HR0X3jABxlDeG811Bd-_2x4ISEl8huiNp-kAG75fBmopT3dd5nSp1E0wCtOxXMFoP6HR9nrg4PfNDxjLao4ulneOOrJMgL4LdtuXgKxrQHm1WDoG4TviJ-lIYXzLVI9Yw-9mjDFihMurEOdJlarrMZekyEVrogy_jliQPo3oXvcemTUiJAZz9UCwq8LOEr09EqzU25kXCXx3Z3B0XvdM6VDUrQN8mKtKU1eU9CVwsLJfOKZ09CpJIwx");'></div>
|
||||||
|
<div>
|
||||||
|
<h3 class="text-sm font-bold text-white group-hover:text-primary transition-colors">Midjourney</h3>
|
||||||
|
<p class="text-xs text-text-secondary truncate max-w-[200px]">midjourney.com</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="p-4">
|
||||||
|
<div class="flex flex-wrap gap-1">
|
||||||
|
<span class="px-2 py-0.5 rounded text-[10px] font-medium bg-[#283539] text-white border border-[#3a4b50]">Image Gen</span>
|
||||||
|
<span class="px-2 py-0.5 rounded text-[10px] font-medium bg-[#283539] text-white border border-[#3a4b50]">Art</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="p-4">
|
||||||
|
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-green-500/10 text-green-400 border border-green-500/20">
|
||||||
|
Active
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td class="p-4 text-right">
|
||||||
|
<div class="flex items-center justify-end gap-2 opacity-0 group-hover:opacity-100 transition-opacity">
|
||||||
|
<button class="p-1.5 rounded-md hover:bg-primary/20 hover:text-primary text-text-secondary transition-colors">
|
||||||
|
<span class="material-symbols-outlined text-[18px]">edit</span>
|
||||||
|
</button>
|
||||||
|
<button class="p-1.5 rounded-md hover:bg-red-500/20 hover:text-red-400 text-text-secondary transition-colors">
|
||||||
|
<span class="material-symbols-outlined text-[18px]">delete</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<!-- Row 3 -->
|
||||||
|
<tr class="group hover:bg-[#1e272c] transition-colors">
|
||||||
|
<td class="p-4">
|
||||||
|
<input class="rounded bg-[#111618] border-[#283539] text-primary focus:ring-offset-[#111618]" type="checkbox"/>
|
||||||
|
</td>
|
||||||
|
<td class="p-4">
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<div class="size-10 rounded-lg bg-cover bg-center shrink-0 border border-[#283539]" data-alt="Icon of Copy AI Tool" style='background-image: url("https://lh3.googleusercontent.com/aida-public/AB6AXuB6XdW_8pAKAs4YPwwYM4bcub_ilJ2i6L7baANaKdNNmDP5k1TRngBOsHE0aTy6sBuBJG49alX-4PRkz55qTcxsrRy6_S8rqD5gCTWLquEDcjqfSai-s7GEdZcBCNJ6G5hEFJM7JFli5r5ETcPq2QBz_NQT0W94XYhzaYpZQVEX-su5ywWxAcrfOFrWrhwC5RCiZL0B7AinM4c1pRSfeDtDpVTzxTvx0MrDi6gBH-duuCUmbr0YjkqhyaZqpRpX-ZhW-JiJQ3sKPUCA");'></div>
|
||||||
|
<div>
|
||||||
|
<h3 class="text-sm font-bold text-white group-hover:text-primary transition-colors">Copy.ai</h3>
|
||||||
|
<p class="text-xs text-text-secondary truncate max-w-[200px]">copy.ai</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="p-4">
|
||||||
|
<div class="flex flex-wrap gap-1">
|
||||||
|
<span class="px-2 py-0.5 rounded text-[10px] font-medium bg-[#283539] text-white border border-[#3a4b50]">Writing</span>
|
||||||
|
<span class="px-2 py-0.5 rounded text-[10px] font-medium bg-[#283539] text-white border border-[#3a4b50]">Marketing</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="p-4">
|
||||||
|
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-yellow-500/10 text-yellow-400 border border-yellow-500/20">
|
||||||
|
Pending
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td class="p-4 text-right">
|
||||||
|
<div class="flex items-center justify-end gap-2 opacity-0 group-hover:opacity-100 transition-opacity">
|
||||||
|
<button class="p-1.5 rounded-md hover:bg-primary/20 hover:text-primary text-text-secondary transition-colors">
|
||||||
|
<span class="material-symbols-outlined text-[18px]">edit</span>
|
||||||
|
</button>
|
||||||
|
<button class="p-1.5 rounded-md hover:bg-red-500/20 hover:text-red-400 text-text-secondary transition-colors">
|
||||||
|
<span class="material-symbols-outlined text-[18px]">delete</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<!-- Pagination -->
|
||||||
|
<div class="flex items-center justify-between px-4 py-3 border-t border-[#283539]">
|
||||||
|
<div class="flex flex-1 justify-between sm:hidden">
|
||||||
|
<a class="relative inline-flex items-center rounded-md border border-[#283539] bg-[#111618] px-4 py-2 text-sm font-medium text-text-secondary hover:bg-[#1e272c] hover:text-white" href="#">Previous</a>
|
||||||
|
<a class="relative ml-3 inline-flex items-center rounded-md border border-[#283539] bg-[#111618] px-4 py-2 text-sm font-medium text-text-secondary hover:bg-[#1e272c] hover:text-white" href="#">Next</a>
|
||||||
|
</div>
|
||||||
|
<div class="hidden sm:flex sm:flex-1 sm:items-center sm:justify-end">
|
||||||
|
<nav aria-label="Pagination" class="isolate inline-flex -space-x-px rounded-md shadow-sm">
|
||||||
|
<a class="relative inline-flex items-center rounded-l-md px-2 py-2 text-text-secondary ring-1 ring-inset ring-[#283539] hover:bg-[#1e272c] focus:z-20 focus:outline-offset-0" href="#">
|
||||||
|
<span class="material-symbols-outlined text-[20px]">chevron_left</span>
|
||||||
|
</a>
|
||||||
|
<a aria-current="page" class="relative z-10 inline-flex items-center bg-primary px-4 py-2 text-sm font-semibold text-[#111618] focus:z-20 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary" href="#">1</a>
|
||||||
|
<a class="relative inline-flex items-center px-4 py-2 text-sm font-semibold text-text-secondary ring-1 ring-inset ring-[#283539] hover:bg-[#1e272c] focus:z-20 focus:outline-offset-0" href="#">2</a>
|
||||||
|
<a class="relative inline-flex items-center px-4 py-2 text-sm font-semibold text-text-secondary ring-1 ring-inset ring-[#283539] hover:bg-[#1e272c] focus:z-20 focus:outline-offset-0" href="#">3</a>
|
||||||
|
<a class="relative inline-flex items-center rounded-r-md px-2 py-2 text-text-secondary ring-1 ring-inset ring-[#283539] hover:bg-[#1e272c] focus:z-20 focus:outline-offset-0" href="#">
|
||||||
|
<span class="material-symbols-outlined text-[20px]">chevron_right</span>
|
||||||
|
</a>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Right Column: Add/Edit Form -->
|
||||||
|
<div class="xl:col-span-1">
|
||||||
|
<div class="bg-surface-dark border border-[#283539] rounded-xl shadow-xl sticky top-8">
|
||||||
|
<div class="px-6 py-4 border-b border-[#283539] flex justify-between items-center bg-[#1e272c] rounded-t-xl">
|
||||||
|
<h2 class="text-lg font-bold text-white">Add New Website</h2>
|
||||||
|
<span class="material-symbols-outlined text-text-secondary cursor-pointer hover:text-white">close</span>
|
||||||
|
</div>
|
||||||
|
<div class="p-6 flex flex-col gap-6">
|
||||||
|
<!-- URL Input + Auto Fetch -->
|
||||||
|
<div class="flex flex-col gap-3">
|
||||||
|
<label class="text-sm font-medium text-white">Website URL <span class="text-primary">*</span></label>
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<div class="relative">
|
||||||
|
<input class="w-full bg-[#111618] border border-[#283539] rounded-lg py-2.5 px-3 text-sm text-white placeholder-text-secondary focus:ring-1 focus:ring-primary focus:border-primary transition-all" placeholder="https://example.com" type="url"/>
|
||||||
|
<div class="absolute inset-y-0 right-3 flex items-center">
|
||||||
|
<span class="material-symbols-outlined text-green-500 text-[18px]" style="display:none;">check_circle</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- CRITICAL: Auto-fetch Button -->
|
||||||
|
<button class="flex items-center justify-center gap-2 w-full py-2 px-3 rounded-lg border border-primary/30 bg-primary/5 hover:bg-primary/10 text-primary text-sm font-medium transition-colors group" type="button">
|
||||||
|
<span class="material-symbols-outlined text-[18px] group-hover:animate-spin">autorenew</span>
|
||||||
|
Auto-fetch Website Info
|
||||||
|
</button>
|
||||||
|
<p class="text-xs text-text-secondary mt-1">
|
||||||
|
Our AI will attempt to scrape metadata and descriptions automatically.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Other Fields -->
|
||||||
|
<div class="flex flex-col gap-3">
|
||||||
|
<label class="text-sm font-medium text-white">Tool Name</label>
|
||||||
|
<input class="w-full bg-[#111618] border border-[#283539] rounded-lg py-2.5 px-3 text-sm text-white placeholder-text-secondary focus:ring-1 focus:ring-primary focus:border-primary" placeholder="e.g. ChatGPT" type="text"/>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-3">
|
||||||
|
<label class="text-sm font-medium text-white">Short Description</label>
|
||||||
|
<textarea class="w-full bg-[#111618] border border-[#283539] rounded-lg py-2.5 px-3 text-sm text-white placeholder-text-secondary focus:ring-1 focus:ring-primary focus:border-primary resize-none" placeholder="Enter a brief description of the AI tool..." rows="4"></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-3">
|
||||||
|
<label class="text-sm font-medium text-white">Tags</label>
|
||||||
|
<div class="p-3 bg-[#111618] border border-[#283539] rounded-lg min-h-[44px] flex flex-wrap gap-2">
|
||||||
|
<span class="inline-flex items-center gap-1 px-2 py-1 rounded bg-[#283539] text-xs text-white">
|
||||||
|
Productivity
|
||||||
|
<span class="material-symbols-outlined text-[14px] cursor-pointer hover:text-red-400">close</span>
|
||||||
|
</span>
|
||||||
|
<input class="bg-transparent border-none p-0 text-sm text-white placeholder-text-secondary focus:ring-0 w-24" placeholder="+ Add tag" type="text"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Status Toggle -->
|
||||||
|
<div class="flex items-center justify-between py-2">
|
||||||
|
<label class="text-sm font-medium text-white">Active Status</label>
|
||||||
|
<button class="w-11 h-6 bg-primary rounded-full relative cursor-pointer">
|
||||||
|
<span class="absolute top-1 left-6 size-4 bg-white rounded-full shadow-sm transition-all"></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="px-6 py-4 border-t border-[#283539] flex gap-3 bg-[#1e272c] rounded-b-xl">
|
||||||
|
<button class="flex-1 py-2.5 rounded-lg bg-primary hover:bg-primary-hover text-[#111618] text-sm font-bold shadow-lg shadow-primary/20 transition-all">
|
||||||
|
Save Website
|
||||||
|
</button>
|
||||||
|
<button class="px-4 py-2.5 rounded-lg border border-[#283539] bg-transparent hover:bg-[#283539] text-white text-sm font-medium transition-colors">
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</body></html>
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 249 KiB |
@@ -0,0 +1,270 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
|
||||||
|
<html class="dark" lang="en"><head>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
||||||
|
<title>AI Tool Detail Page</title>
|
||||||
|
<!-- Google Fonts -->
|
||||||
|
<link href="https://fonts.googleapis.com" rel="preconnect"/>
|
||||||
|
<link crossorigin="" href="https://fonts.gstatic.com" rel="preconnect"/>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&family=Noto+Sans:wght@400;500;700&display=swap" rel="stylesheet"/>
|
||||||
|
<!-- Material Symbols -->
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
||||||
|
<!-- Tailwind CSS -->
|
||||||
|
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
||||||
|
<!-- Theme Config -->
|
||||||
|
<script id="tailwind-config">
|
||||||
|
tailwind.config = {
|
||||||
|
darkMode: "class",
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
colors: {
|
||||||
|
"primary": "#25c0f4",
|
||||||
|
"primary-dark": "#0ea5e9",
|
||||||
|
"secondary": "#a855f7", // Adding purple for the gradient request
|
||||||
|
"background-light": "#f5f8f8",
|
||||||
|
"background-dark": "#101e22",
|
||||||
|
"surface-dark": "#1a262b",
|
||||||
|
"surface-border": "#283539",
|
||||||
|
"text-secondary": "#9cb2ba"
|
||||||
|
},
|
||||||
|
fontFamily: {
|
||||||
|
"display": ["Space Grotesk", "sans-serif"],
|
||||||
|
"body": ["Noto Sans", "sans-serif"]
|
||||||
|
},
|
||||||
|
borderRadius: {"DEFAULT": "0.25rem", "lg": "0.5rem", "xl": "0.75rem", "2xl": "1rem", "full": "9999px"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: 'Space Grotesk', sans-serif;
|
||||||
|
}
|
||||||
|
.glass-card {
|
||||||
|
background: rgba(26, 38, 43, 0.6);
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
|
border: 1px solid rgba(40, 53, 57, 0.5);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body class="bg-background-light dark:bg-background-dark text-slate-900 dark:text-white min-h-screen flex flex-col selection:bg-primary selection:text-background-dark">
|
||||||
|
<!-- Top Navigation -->
|
||||||
|
<div class="w-full border-b border-surface-border bg-background-dark/95 backdrop-blur sticky top-0 z-50">
|
||||||
|
<div class="max-w-[1280px] mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<header class="flex items-center justify-between h-16">
|
||||||
|
<!-- Logo Area -->
|
||||||
|
<div class="flex items-center gap-8">
|
||||||
|
<div class="flex items-center gap-3 text-white">
|
||||||
|
<div class="size-8 text-primary">
|
||||||
|
<span class="material-symbols-outlined text-3xl">token</span>
|
||||||
|
</div>
|
||||||
|
<h2 class="text-white text-xl font-bold tracking-tight">AI Discovery</h2>
|
||||||
|
</div>
|
||||||
|
<!-- Desktop Nav -->
|
||||||
|
<div class="hidden md:flex items-center gap-6">
|
||||||
|
<a class="text-text-secondary hover:text-white text-sm font-medium transition-colors" href="#">Categories</a>
|
||||||
|
<a class="text-text-secondary hover:text-white text-sm font-medium transition-colors" href="#">Submit Tool</a>
|
||||||
|
<a class="text-text-secondary hover:text-white text-sm font-medium transition-colors" href="#">Blog</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Right Actions -->
|
||||||
|
<div class="flex items-center gap-4">
|
||||||
|
<div class="hidden sm:flex relative group">
|
||||||
|
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||||
|
<span class="material-symbols-outlined text-text-secondary text-[20px]">search</span>
|
||||||
|
</div>
|
||||||
|
<input class="bg-surface-border/50 hover:bg-surface-border focus:bg-surface-border text-white text-sm rounded-lg pl-10 pr-4 py-2 w-48 focus:w-64 transition-all outline-none ring-0 border border-transparent focus:border-primary/50 placeholder:text-text-secondary/70" placeholder="Search tools..."/>
|
||||||
|
</div>
|
||||||
|
<button class="flex items-center justify-center rounded-lg h-9 px-4 bg-primary hover:bg-primary-dark text-background-dark text-sm font-bold transition-colors">
|
||||||
|
Log In
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Main Layout -->
|
||||||
|
<main class="flex-grow w-full max-w-[1280px] mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||||
|
<!-- Back Button -->
|
||||||
|
<div class="mb-8">
|
||||||
|
<button class="group flex items-center gap-2 text-text-secondary hover:text-primary transition-colors text-sm font-bold">
|
||||||
|
<span class="material-symbols-outlined text-[20px] group-hover:-translate-x-1 transition-transform">arrow_back</span>
|
||||||
|
<span>Back to Home</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-1 lg:grid-cols-12 gap-8 lg:gap-12">
|
||||||
|
<!-- Left Column: Header Info + Content -->
|
||||||
|
<div class="lg:col-span-8 flex flex-col gap-8">
|
||||||
|
<!-- Header Section -->
|
||||||
|
<div class="flex flex-col sm:flex-row gap-6 items-start">
|
||||||
|
<!-- Logo Container -->
|
||||||
|
<div class="relative shrink-0">
|
||||||
|
<div class="absolute -inset-1 bg-gradient-to-br from-primary via-purple-500 to-secondary rounded-2xl opacity-30 blur-md"></div>
|
||||||
|
<div class="relative size-[120px] rounded-xl bg-surface-border flex items-center justify-center overflow-hidden border border-surface-border bg-cover bg-center" data-alt="Abstract neural network pattern in deep blue and purple" style="background-image: url('https://lh3.googleusercontent.com/aida-public/AB6AXuAFm-_1orioA8YwgKaNGCzxomQeon7z13YmJCs2VpIoXP6J2a5kLgu_mC9IFA-cRA5s6yNaDPiWRxhF8tTX4FFOTbngaHydfCmAMNxU2EuZ2burR-hLOggJYl24v1Ry7GaNu6ETcYui4SIfvWqNs43vwA4vEmoogV3TxVzuDRDHsy5nj9ab4TnW4c5mVgqHPttLIF1NKTm6rtEcPnWW1jtCihIAAqZVnst9o0Z-IyAYGJoPjNajRdJZGR5NDfiBf-tdr79Bo42KY_DX');">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Title & Meta -->
|
||||||
|
<div class="flex flex-col gap-3 flex-1">
|
||||||
|
<div>
|
||||||
|
<h1 class="text-4xl font-bold text-white tracking-tight font-display mb-1">NeuroGen AI</h1>
|
||||||
|
<a class="text-primary hover:text-primary-dark hover:underline text-base font-medium flex items-center gap-1" href="#">
|
||||||
|
www.neurogen.ai
|
||||||
|
<span class="material-symbols-outlined text-[16px]">open_in_new</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<!-- Tags -->
|
||||||
|
<div class="flex flex-wrap gap-2">
|
||||||
|
<span class="inline-flex items-center px-3 py-1 rounded-full text-xs font-medium bg-surface-border text-text-secondary border border-transparent hover:border-primary/30 transition-colors cursor-default">Generative AI</span>
|
||||||
|
<span class="inline-flex items-center px-3 py-1 rounded-full text-xs font-medium bg-surface-border text-text-secondary border border-transparent hover:border-primary/30 transition-colors cursor-default">Copywriting</span>
|
||||||
|
<span class="inline-flex items-center px-3 py-1 rounded-full text-xs font-medium bg-surface-border text-text-secondary border border-transparent hover:border-primary/30 transition-colors cursor-default">Productivity</span>
|
||||||
|
<span class="inline-flex items-center px-3 py-1 rounded-full text-xs font-medium bg-purple-500/10 text-purple-400 border border-purple-500/20">Free Trial</span>
|
||||||
|
</div>
|
||||||
|
<!-- Stats Row -->
|
||||||
|
<div class="flex items-center gap-6 mt-1 text-text-secondary text-sm">
|
||||||
|
<div class="flex items-center gap-2" title="Total Views">
|
||||||
|
<span class="material-symbols-outlined text-[18px]">visibility</span>
|
||||||
|
<span>12,450 Views</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-2" title="Date Added">
|
||||||
|
<span class="material-symbols-outlined text-[18px]">calendar_today</span>
|
||||||
|
<span>Added Oct 24, 2023</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr class="border-surface-border/60"/>
|
||||||
|
<!-- Main Content Body -->
|
||||||
|
<div class="space-y-8">
|
||||||
|
<!-- Overview -->
|
||||||
|
<section>
|
||||||
|
<h3 class="text-xl font-bold text-white mb-4 flex items-center gap-2">
|
||||||
|
<span class="material-symbols-outlined text-primary">info</span>
|
||||||
|
Product Overview
|
||||||
|
</h3>
|
||||||
|
<p class="text-text-secondary leading-relaxed text-lg">
|
||||||
|
NeuroGen AI is an advanced copywriting assistant designed to help marketers, writers, and businesses generate high-converting content in seconds. By leveraging state-of-the-art natural language processing models, it understands context, tone, and brand voice to deliver tailored outputs.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
<!-- Image Gallery / Preview (Optional visual break) -->
|
||||||
|
<div class="rounded-xl overflow-hidden border border-surface-border relative group aspect-video w-full bg-surface-dark">
|
||||||
|
<div class="absolute inset-0 bg-cover bg-center" data-alt="Dashboard interface of NeuroGen AI showing analytics and text editor" style="background-image: url('https://lh3.googleusercontent.com/aida-public/AB6AXuCtwM-4OkGIPG4sQqABpRcLvOgKvLzuyVlQJUVcU8SqFsVod_jck_vFm-q7UX70vUaaF7UPEa--rkf18dMi5_qYcXYsjTzd5ehM3ga_OoIr1r6wnRTnRxKcrUUC5USP72vdsFJkRFeKmH6epH1Avi5tglJ732VTvoNiuys499jctwZ67d4Yu8Slfn0EO4Ny8zIjzPRGLer8lMaEeZPMagNbgn4asMLvmDtTep3mgPzwTscfzoOFVChGqNak0ZmLyOiVY7vKIapdHyoz');">
|
||||||
|
</div>
|
||||||
|
<div class="absolute inset-0 bg-gradient-to-t from-background-dark via-transparent to-transparent opacity-60"></div>
|
||||||
|
<div class="absolute bottom-4 left-4">
|
||||||
|
<p class="text-white font-medium text-sm bg-black/50 backdrop-blur px-3 py-1 rounded-lg">Dashboard Interface</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Detailed Description -->
|
||||||
|
<section>
|
||||||
|
<h3 class="text-xl font-bold text-white mb-4">Detailed Description</h3>
|
||||||
|
<div class="text-text-secondary space-y-4 leading-relaxed">
|
||||||
|
<p>
|
||||||
|
Writing high-quality content consistently is a challenge for modern businesses. NeuroGen AI bridges the gap between human creativity and machine speed. Unlike generic text generators, NeuroGen allows users to fine-tune specific parameters such as emotional resonance, sentence structure complexity, and SEO keyword density.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
The platform includes specialized templates for:
|
||||||
|
</p>
|
||||||
|
<ul class="list-disc pl-5 space-y-2 text-gray-400 marker:text-primary">
|
||||||
|
<li>Social media captions (Instagram, LinkedIn, Twitter)</li>
|
||||||
|
<li>Long-form blog posts with automatic formatting</li>
|
||||||
|
<li>Email marketing sequences</li>
|
||||||
|
<li>Ad copy variants for A/B testing</li>
|
||||||
|
</ul>
|
||||||
|
<p>
|
||||||
|
Security is paramount; NeuroGen ensures that your proprietary data is never used to train public models. Enterprise-grade encryption and team collaboration features make it a suitable choice for large organizations looking to scale their content operations.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Right Column: Sidebar -->
|
||||||
|
<div class="lg:col-span-4 space-y-6">
|
||||||
|
<!-- CTA Card -->
|
||||||
|
<div class="glass-card rounded-2xl p-6 flex flex-col gap-4 shadow-xl shadow-black/20 sticky top-24">
|
||||||
|
<div class="flex items-center justify-between mb-2">
|
||||||
|
<span class="text-white font-bold text-lg">Try it now</span>
|
||||||
|
<span class="px-2 py-1 rounded bg-green-500/20 text-green-400 text-xs font-bold border border-green-500/20">ONLINE</span>
|
||||||
|
</div>
|
||||||
|
<a class="group relative flex items-center justify-center w-full overflow-hidden rounded-xl bg-gradient-to-r from-primary via-primary to-purple-500 p-[1px] focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 focus:ring-offset-slate-50 transition-all hover:scale-[1.02]" href="#">
|
||||||
|
<span class="absolute inset-[-1000%] animate-[spin_2s_linear_infinite] bg-[conic-gradient(from_90deg_at_50%_50%,#E2CBFF_0%,#393BB2_50%,#E2CBFF_100%)] opacity-0 group-hover:opacity-100 transition-opacity"></span>
|
||||||
|
<div class="relative flex h-12 w-full items-center justify-center rounded-xl bg-background-dark/10 group-hover:bg-transparent px-8 py-1 backdrop-blur-3xl transition-colors">
|
||||||
|
<span class="flex items-center gap-2 text-background-dark font-bold text-base uppercase tracking-wide">
|
||||||
|
Visit Website
|
||||||
|
<span class="material-symbols-outlined text-[20px] font-bold">arrow_outward</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<!-- Overlay for text color correction on hover/default since bg changes -->
|
||||||
|
<div class="absolute inset-0 flex items-center justify-center pointer-events-none">
|
||||||
|
<span class="flex items-center gap-2 text-white font-bold text-base uppercase tracking-wide group-hover:text-white transition-colors">
|
||||||
|
Visit Website
|
||||||
|
<span class="material-symbols-outlined text-[20px] font-bold">arrow_outward</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<p class="text-xs text-center text-text-secondary">
|
||||||
|
Opens in a new tab • <span class="text-white">neurogen.ai</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<!-- Features Box -->
|
||||||
|
<div class="rounded-2xl border border-surface-border bg-surface-dark/50 p-6">
|
||||||
|
<h3 class="text-lg font-bold text-white mb-5 flex items-center gap-2">
|
||||||
|
<span class="material-symbols-outlined text-secondary">verified</span>
|
||||||
|
Main Features
|
||||||
|
</h3>
|
||||||
|
<ul class="space-y-4">
|
||||||
|
<li class="flex items-start gap-3">
|
||||||
|
<div class="mt-0.5 rounded-full bg-primary/20 p-1">
|
||||||
|
<span class="material-symbols-outlined text-primary text-[16px] block">check</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p class="text-sm font-bold text-white">Contextual Awareness</p>
|
||||||
|
<p class="text-xs text-text-secondary mt-0.5">Understands previous inputs to maintain thread continuity.</p>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="flex items-start gap-3">
|
||||||
|
<div class="mt-0.5 rounded-full bg-primary/20 p-1">
|
||||||
|
<span class="material-symbols-outlined text-primary text-[16px] block">check</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p class="text-sm font-bold text-white">Multi-language Support</p>
|
||||||
|
<p class="text-xs text-text-secondary mt-0.5">Generate content in over 25 languages natively.</p>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="flex items-start gap-3">
|
||||||
|
<div class="mt-0.5 rounded-full bg-primary/20 p-1">
|
||||||
|
<span class="material-symbols-outlined text-primary text-[16px] block">check</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p class="text-sm font-bold text-white">SEO Optimization</p>
|
||||||
|
<p class="text-xs text-text-secondary mt-0.5">Built-in keyword analyzer and suggestion tool.</p>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="flex items-start gap-3">
|
||||||
|
<div class="mt-0.5 rounded-full bg-primary/20 p-1">
|
||||||
|
<span class="material-symbols-outlined text-primary text-[16px] block">check</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p class="text-sm font-bold text-white">Export to CMS</p>
|
||||||
|
<p class="text-xs text-text-secondary mt-0.5">Direct integration with WordPress and Ghost.</p>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<!-- Share / Report -->
|
||||||
|
<div class="flex gap-4">
|
||||||
|
<button class="flex-1 py-3 rounded-lg border border-surface-border bg-transparent text-text-secondary hover:text-white hover:bg-surface-border transition-colors text-sm font-medium flex items-center justify-center gap-2">
|
||||||
|
<span class="material-symbols-outlined text-[18px]">share</span>
|
||||||
|
Share
|
||||||
|
</button>
|
||||||
|
<button class="flex-1 py-3 rounded-lg border border-surface-border bg-transparent text-text-secondary hover:text-red-400 hover:border-red-400/30 hover:bg-red-400/10 transition-colors text-sm font-medium flex items-center justify-center gap-2">
|
||||||
|
<span class="material-symbols-outlined text-[18px]">flag</span>
|
||||||
|
Report
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Footer Spacer -->
|
||||||
|
<div class="h-20"></div>
|
||||||
|
</main>
|
||||||
|
</body></html>
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 654 KiB |
@@ -0,0 +1,383 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
|
||||||
|
<html class="dark" lang="en"><head>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
||||||
|
<title>ZJPB - AI Tool Discovery</title>
|
||||||
|
<!-- Google Fonts -->
|
||||||
|
<link href="https://fonts.googleapis.com" rel="preconnect"/>
|
||||||
|
<link crossorigin="" href="https://fonts.gstatic.com" rel="preconnect"/>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&display=swap" rel="stylesheet"/>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans:wght@400;500;700&display=swap" rel="stylesheet"/>
|
||||||
|
<!-- Material Symbols -->
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
||||||
|
<!-- Tailwind CSS -->
|
||||||
|
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
||||||
|
<script id="tailwind-config">
|
||||||
|
tailwind.config = {
|
||||||
|
darkMode: "class",
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
colors: {
|
||||||
|
primary: "#25c0f4",
|
||||||
|
secondary: "#7c3aed", // Adding purple for the gradient mix
|
||||||
|
"background-light": "#f5f8f8",
|
||||||
|
"background-dark": "#111618",
|
||||||
|
"surface-dark": "#1b2427",
|
||||||
|
"border-dark": "#283539",
|
||||||
|
},
|
||||||
|
fontFamily: {
|
||||||
|
"display": ["Space Grotesk", "sans-serif"],
|
||||||
|
"body": ["Noto Sans", "sans-serif"],
|
||||||
|
},
|
||||||
|
backgroundImage: {
|
||||||
|
'gradient-tech': 'linear-gradient(135deg, #25c0f4 0%, #7c3aed 100%)',
|
||||||
|
'gradient-text': 'linear-gradient(to right, #25c0f4, #a855f7)',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: 'Space Grotesk', sans-serif;
|
||||||
|
}
|
||||||
|
.text-gradient {
|
||||||
|
background: linear-gradient(to right, #25c0f4, #c084fc);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
}
|
||||||
|
.card-hover:hover {
|
||||||
|
transform: translateY(-4px);
|
||||||
|
box-shadow: 0 10px 30px -10px rgba(37, 192, 244, 0.15);
|
||||||
|
border-color: #25c0f4;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body class="bg-background-light dark:bg-background-dark text-slate-900 dark:text-white antialiased selection:bg-primary selection:text-black">
|
||||||
|
<div class="relative flex min-h-screen w-full flex-col overflow-x-hidden">
|
||||||
|
<!-- Background Gradient Elements for Tech Vibe -->
|
||||||
|
<div class="fixed top-0 left-0 w-full h-96 bg-primary/5 blur-[120px] rounded-full pointer-events-none -translate-y-1/2"></div>
|
||||||
|
<div class="fixed top-20 right-0 w-96 h-96 bg-secondary/10 blur-[100px] rounded-full pointer-events-none translate-x-1/3"></div>
|
||||||
|
<!-- Top Navigation -->
|
||||||
|
<header class="sticky top-0 z-50 w-full border-b border-solid border-border-dark bg-background-dark/80 backdrop-blur-md">
|
||||||
|
<div class="mx-auto flex h-16 max-w-[1440px] items-center justify-between px-6 lg:px-10">
|
||||||
|
<div class="flex items-center gap-8">
|
||||||
|
<!-- Logo -->
|
||||||
|
<a class="flex items-center gap-3 text-white hover:opacity-90 transition-opacity" href="#">
|
||||||
|
<div class="flex items-center justify-center size-8 rounded-lg bg-gradient-tech text-black">
|
||||||
|
<span class="material-symbols-outlined" style="font-size: 20px; font-weight: 700;">bolt</span>
|
||||||
|
</div>
|
||||||
|
<h2 class="text-white text-lg font-bold leading-tight tracking-tight">ZJPB</h2>
|
||||||
|
</a>
|
||||||
|
<!-- Desktop Nav -->
|
||||||
|
<nav class="hidden md:flex items-center gap-8">
|
||||||
|
<a class="text-gray-300 hover:text-primary text-sm font-medium transition-colors" href="#">Home</a>
|
||||||
|
<a class="text-gray-300 hover:text-primary text-sm font-medium transition-colors" href="#">Categories</a>
|
||||||
|
<a class="text-gray-300 hover:text-primary text-sm font-medium transition-colors" href="#">Community</a>
|
||||||
|
<a class="text-gray-300 hover:text-primary text-sm font-medium transition-colors" href="#">About</a>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
<!-- Actions -->
|
||||||
|
<div class="flex items-center gap-4">
|
||||||
|
<!-- Search -->
|
||||||
|
<div class="hidden lg:flex items-center bg-surface-dark border border-border-dark rounded-full h-10 px-4 w-64 focus-within:border-primary transition-colors">
|
||||||
|
<span class="material-symbols-outlined text-gray-400" style="font-size: 20px;">search</span>
|
||||||
|
<input class="bg-transparent border-none text-white text-sm placeholder-gray-500 focus:ring-0 w-full ml-2" placeholder="Search AI tools..." type="text"/>
|
||||||
|
</div>
|
||||||
|
<!-- Auth Buttons -->
|
||||||
|
<button class="hidden sm:flex h-9 px-4 items-center justify-center rounded-lg text-sm font-bold text-white hover:bg-white/5 transition-colors">
|
||||||
|
Log In
|
||||||
|
</button>
|
||||||
|
<button class="flex h-9 px-5 items-center justify-center rounded-lg bg-primary hover:bg-primary/90 text-background-dark text-sm font-bold transition-colors shadow-[0_0_15px_rgba(37,192,244,0.3)]">
|
||||||
|
Sign Up
|
||||||
|
</button>
|
||||||
|
<!-- Mobile Menu Toggle -->
|
||||||
|
<button class="md:hidden text-white">
|
||||||
|
<span class="material-symbols-outlined">menu</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<!-- Main Content -->
|
||||||
|
<main class="flex-1 flex flex-col items-center w-full px-6 lg:px-10 py-8">
|
||||||
|
<div class="w-full max-w-[1200px] flex flex-col gap-10">
|
||||||
|
<!-- Hero Section -->
|
||||||
|
<div class="flex flex-col md:flex-row items-start md:items-end justify-between gap-6 pb-6 border-b border-border-dark/50">
|
||||||
|
<div class="flex flex-col gap-2 max-w-2xl">
|
||||||
|
<h1 class="text-4xl md:text-5xl lg:text-6xl font-black tracking-tighter text-white mb-2">
|
||||||
|
ZJPB - <span class="text-gradient">焦提示词</span>
|
||||||
|
</h1>
|
||||||
|
<p class="text-gray-400 text-lg md:text-xl font-light">
|
||||||
|
发现最新最好用的AI工具和产品. Discover the best AI tools tailored for your workflow.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<button class="flex items-center gap-2 bg-surface-dark border border-border-dark hover:border-primary text-white px-5 py-2.5 rounded-lg transition-all group whitespace-nowrap">
|
||||||
|
<span class="material-symbols-outlined text-primary group-hover:scale-110 transition-transform">add_circle</span>
|
||||||
|
<span class="font-bold text-sm">Submit a Tool</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<!-- Filter Chips -->
|
||||||
|
<div class="w-full overflow-x-auto pb-2 scrollbar-hide">
|
||||||
|
<div class="flex gap-3 min-w-max">
|
||||||
|
<button class="flex items-center h-9 px-5 rounded-full bg-primary text-background-dark font-bold text-sm shadow-[0_0_10px_rgba(37,192,244,0.4)]">
|
||||||
|
All Tools
|
||||||
|
</button>
|
||||||
|
<button class="flex items-center gap-2 h-9 px-5 rounded-full bg-surface-dark border border-border-dark text-gray-300 hover:text-white hover:border-gray-500 font-medium text-sm transition-all">
|
||||||
|
<span class="material-symbols-outlined text-sm">chat</span> AI Chat
|
||||||
|
</button>
|
||||||
|
<button class="flex items-center gap-2 h-9 px-5 rounded-full bg-surface-dark border border-border-dark text-gray-300 hover:text-white hover:border-gray-500 font-medium text-sm transition-all">
|
||||||
|
<span class="material-symbols-outlined text-sm">image</span> Image Gen
|
||||||
|
</button>
|
||||||
|
<button class="flex items-center gap-2 h-9 px-5 rounded-full bg-surface-dark border border-border-dark text-gray-300 hover:text-white hover:border-gray-500 font-medium text-sm transition-all">
|
||||||
|
<span class="material-symbols-outlined text-sm">movie</span> Video
|
||||||
|
</button>
|
||||||
|
<button class="flex items-center gap-2 h-9 px-5 rounded-full bg-surface-dark border border-border-dark text-gray-300 hover:text-white hover:border-gray-500 font-medium text-sm transition-all">
|
||||||
|
<span class="material-symbols-outlined text-sm">edit_note</span> Writing
|
||||||
|
</button>
|
||||||
|
<button class="flex items-center gap-2 h-9 px-5 rounded-full bg-surface-dark border border-border-dark text-gray-300 hover:text-white hover:border-gray-500 font-medium text-sm transition-all">
|
||||||
|
<span class="material-symbols-outlined text-sm">code</span> Coding
|
||||||
|
</button>
|
||||||
|
<button class="flex items-center gap-2 h-9 px-5 rounded-full bg-surface-dark border border-border-dark text-gray-300 hover:text-white hover:border-gray-500 font-medium text-sm transition-all">
|
||||||
|
<span class="material-symbols-outlined text-sm">graphic_eq</span> Audio
|
||||||
|
</button>
|
||||||
|
<button class="flex items-center gap-2 h-9 px-5 rounded-full bg-surface-dark border border-border-dark text-gray-300 hover:text-white hover:border-gray-500 font-medium text-sm transition-all">
|
||||||
|
<span class="material-symbols-outlined text-sm">view_in_ar</span> 3D Assets
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Tool Grid -->
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
|
||||||
|
<!-- Card 1 -->
|
||||||
|
<div class="group relative flex flex-col gap-4 rounded-xl border border-border-dark bg-surface-dark p-5 card-hover transition-all duration-300 cursor-pointer overflow-hidden">
|
||||||
|
<div class="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-primary to-secondary opacity-0 group-hover:opacity-100 transition-opacity"></div>
|
||||||
|
<div class="flex items-start justify-between">
|
||||||
|
<div class="size-12 rounded-lg bg-cover bg-center shadow-lg" data-alt="ChatGPT logo icon green background" style="background-image: url('https://lh3.googleusercontent.com/aida-public/AB6AXuBStb1HsLpCc4Hlu8FzN0ecAT6pMnnu_pzF4cUUQDG6FdYLA65ua60D-3NxKDOHqkuVsgsy9ku-vUZ0_Jyc5O9Xi1NlwR5L7oC9gt_jretgJqsEeOk1dm2yo4GvB26KgmMWzpUKeCtIg1NyZDWRNJF3gfIVxF95sT29iy6tKSXsUdfVsOo-o_5kEpB3qCFKZdMo4fhOe8DVh4JnmmdZuS8z3PflupzZpru6F0QK3xL407vfIULuQ3L5NRtFdTfZs7O-elYl2FrwjJ_x');"></div>
|
||||||
|
<div class="flex items-center justify-center size-8 rounded-full bg-background-dark text-gray-400 group-hover:text-primary group-hover:bg-primary/10 transition-colors">
|
||||||
|
<span class="material-symbols-outlined text-lg">arrow_outward</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 class="text-white text-lg font-bold leading-tight group-hover:text-primary transition-colors">ChatGPT</h3>
|
||||||
|
<p class="text-gray-400 text-sm mt-2 line-clamp-2">OpenAI's advanced conversational model capable of understanding and generating human-like text.</p>
|
||||||
|
</div>
|
||||||
|
<div class="mt-auto flex items-center justify-between pt-4 border-t border-border-dark">
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<span class="px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider bg-white/5 text-gray-300 border border-white/10">Chat</span>
|
||||||
|
<span class="px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider bg-white/5 text-gray-300 border border-white/10">NLP</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-1 text-gray-500 text-xs">
|
||||||
|
<span class="material-symbols-outlined text-[14px]">visibility</span>
|
||||||
|
<span>1.2M</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Card 2 -->
|
||||||
|
<div class="group relative flex flex-col gap-4 rounded-xl border border-border-dark bg-surface-dark p-5 card-hover transition-all duration-300 cursor-pointer overflow-hidden">
|
||||||
|
<div class="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-primary to-secondary opacity-0 group-hover:opacity-100 transition-opacity"></div>
|
||||||
|
<div class="flex items-start justify-between">
|
||||||
|
<div class="size-12 rounded-lg bg-cover bg-center shadow-lg" data-alt="Midjourney logo icon blue background" style="background-image: url('https://lh3.googleusercontent.com/aida-public/AB6AXuBW8nuPBGShgvw9aCK5p0mKlki3EMfhi8ZZKvLTaM-QupUQDzsBx5wkIvN_Unyt30H2WHXBw5GB4Kz8gL68peWqK99Cpo6gRM-Bk3f94xWdYji9osPrreu5UMVtu2W1Rn8IyBCfPDUu3LJnbDd_viZz4nb04tliq9O_ezo8OCYMede7GZYxIxHTrEkhNBADzC2Z7KjeF-DR7fRBf4dxHYz5rJUGakf1qyEouaAlEDWMnBymOKFkQGK19UvizQiQIUeDFMGMxgL0ng_x');"></div>
|
||||||
|
<div class="flex items-center justify-center size-8 rounded-full bg-background-dark text-gray-400 group-hover:text-primary group-hover:bg-primary/10 transition-colors">
|
||||||
|
<span class="material-symbols-outlined text-lg">arrow_outward</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 class="text-white text-lg font-bold leading-tight group-hover:text-primary transition-colors">Midjourney</h3>
|
||||||
|
<p class="text-gray-400 text-sm mt-2 line-clamp-2">Hyper-realistic AI image generator that creates stunning visuals from text prompts.</p>
|
||||||
|
</div>
|
||||||
|
<div class="mt-auto flex items-center justify-between pt-4 border-t border-border-dark">
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<span class="px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider bg-white/5 text-gray-300 border border-white/10">Image</span>
|
||||||
|
<span class="px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider bg-white/5 text-gray-300 border border-white/10">Art</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-1 text-gray-500 text-xs">
|
||||||
|
<span class="material-symbols-outlined text-[14px]">visibility</span>
|
||||||
|
<span>850k</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Card 3 -->
|
||||||
|
<div class="group relative flex flex-col gap-4 rounded-xl border border-border-dark bg-surface-dark p-5 card-hover transition-all duration-300 cursor-pointer overflow-hidden">
|
||||||
|
<div class="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-primary to-secondary opacity-0 group-hover:opacity-100 transition-opacity"></div>
|
||||||
|
<div class="flex items-start justify-between">
|
||||||
|
<div class="size-12 rounded-lg bg-cover bg-center shadow-lg" data-alt="Jasper logo icon yellow background" style="background-image: url('https://lh3.googleusercontent.com/aida-public/AB6AXuAQv96KYBZ3uV5NpDJyuDzc5J-mGo3fTWcBzVv00wC062ApfHezk6XkvKI6lsad5hFopxB7Qqhbqifto971lBo_7ASfOFUvtSU-Z3omU0q4PGcmea1YR_Va7vKPBtpA1DS_uq779QNTY5oF_kjapJrp7-Nfi5tI0NfQ3kU5KOd-dwBrN2T1Md6nT1jUEeOKG3zEgmAfEYan4mQXQXpz7Ywh2ZaoFCOY0OTiJP9RAnzIYL0Tv-ywt3_r66CSTY5pyzfqqP7sUk4vegAv');"></div>
|
||||||
|
<div class="flex items-center justify-center size-8 rounded-full bg-background-dark text-gray-400 group-hover:text-primary group-hover:bg-primary/10 transition-colors">
|
||||||
|
<span class="material-symbols-outlined text-lg">arrow_outward</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 class="text-white text-lg font-bold leading-tight group-hover:text-primary transition-colors">Jasper AI</h3>
|
||||||
|
<p class="text-gray-400 text-sm mt-2 line-clamp-2">AI copywriter for marketing content, blog posts, and social media captions.</p>
|
||||||
|
</div>
|
||||||
|
<div class="mt-auto flex items-center justify-between pt-4 border-t border-border-dark">
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<span class="px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider bg-white/5 text-gray-300 border border-white/10">Writing</span>
|
||||||
|
<span class="px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider bg-white/5 text-gray-300 border border-white/10">Marketing</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-1 text-gray-500 text-xs">
|
||||||
|
<span class="material-symbols-outlined text-[14px]">visibility</span>
|
||||||
|
<span>300k</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Card 4 -->
|
||||||
|
<div class="group relative flex flex-col gap-4 rounded-xl border border-border-dark bg-surface-dark p-5 card-hover transition-all duration-300 cursor-pointer overflow-hidden">
|
||||||
|
<div class="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-primary to-secondary opacity-0 group-hover:opacity-100 transition-opacity"></div>
|
||||||
|
<div class="flex items-start justify-between">
|
||||||
|
<div class="size-12 rounded-lg bg-cover bg-center shadow-lg" data-alt="Runway logo icon pink background" style="background-image: url('https://lh3.googleusercontent.com/aida-public/AB6AXuAkmxk1Ar1KxgHHM5pKMer4rOra0KUieDAXhm9nE4sQp5WjsnGYENKmbM-EN-hRjhV7OC2ha7Go-Cl90HgfNpbgt--grIpJYKoUjgvsp0juLIDgX_fN4rKrjdkezxRU8YVJBuTaM3lWZJCptm3I6isDO10xidrChOc5kWV9SQny79KEVKENXxOJXEVT3c0m16M3JnGRTRmu-6EY8XU68-On78-7SXxLyP5TESi-ooZ2wHaOwxqJUwFb-oQyQoKAkXnzXAqGwwITBiLS');"></div>
|
||||||
|
<div class="flex items-center justify-center size-8 rounded-full bg-background-dark text-gray-400 group-hover:text-primary group-hover:bg-primary/10 transition-colors">
|
||||||
|
<span class="material-symbols-outlined text-lg">arrow_outward</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 class="text-white text-lg font-bold leading-tight group-hover:text-primary transition-colors">Runway Gen-2</h3>
|
||||||
|
<p class="text-gray-400 text-sm mt-2 line-clamp-2">Next-generation video creation tool that turns text into high-quality video clips.</p>
|
||||||
|
</div>
|
||||||
|
<div class="mt-auto flex items-center justify-between pt-4 border-t border-border-dark">
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<span class="px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider bg-white/5 text-gray-300 border border-white/10">Video</span>
|
||||||
|
<span class="px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider bg-white/5 text-gray-300 border border-white/10">Gen-AI</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-1 text-gray-500 text-xs">
|
||||||
|
<span class="material-symbols-outlined text-[14px]">visibility</span>
|
||||||
|
<span>420k</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Card 5 -->
|
||||||
|
<div class="group relative flex flex-col gap-4 rounded-xl border border-border-dark bg-surface-dark p-5 card-hover transition-all duration-300 cursor-pointer overflow-hidden">
|
||||||
|
<div class="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-primary to-secondary opacity-0 group-hover:opacity-100 transition-opacity"></div>
|
||||||
|
<div class="flex items-start justify-between">
|
||||||
|
<div class="size-12 rounded-lg bg-cover bg-center shadow-lg" data-alt="Copilot logo icon purple background" style="background-image: url('https://lh3.googleusercontent.com/aida-public/AB6AXuBki2piUPdnWstozizUU63ouakjjA3B4xN5uQs7rA84yX4NsYFIaNybtfVRy0bXUKfUBYdixo22jb3bbN6TlKindW5iQU0sJc3FF2GXS_CRpgLoNMLziOyf4rLArNmM2VoycHJ3DyrIsI8847xlO3BHrK8kyemNc9mBfaXyPnWsJ0hnF2lXfljCOcYNQTtY1qgzw5RfOHwA0NUnLwuRG53tU-mnJPPejbDyzW7YWdBnh9yTj3dnJJDOfzYiEx252wgf7Q6GBjTGrQnG');"></div>
|
||||||
|
<div class="flex items-center justify-center size-8 rounded-full bg-background-dark text-gray-400 group-hover:text-primary group-hover:bg-primary/10 transition-colors">
|
||||||
|
<span class="material-symbols-outlined text-lg">arrow_outward</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 class="text-white text-lg font-bold leading-tight group-hover:text-primary transition-colors">GitHub Copilot</h3>
|
||||||
|
<p class="text-gray-400 text-sm mt-2 line-clamp-2">Your AI pair programmer that helps you write better code faster.</p>
|
||||||
|
</div>
|
||||||
|
<div class="mt-auto flex items-center justify-between pt-4 border-t border-border-dark">
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<span class="px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider bg-white/5 text-gray-300 border border-white/10">Dev</span>
|
||||||
|
<span class="px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider bg-white/5 text-gray-300 border border-white/10">Coding</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-1 text-gray-500 text-xs">
|
||||||
|
<span class="material-symbols-outlined text-[14px]">visibility</span>
|
||||||
|
<span>1.5M</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Card 6 -->
|
||||||
|
<div class="group relative flex flex-col gap-4 rounded-xl border border-border-dark bg-surface-dark p-5 card-hover transition-all duration-300 cursor-pointer overflow-hidden">
|
||||||
|
<div class="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-primary to-secondary opacity-0 group-hover:opacity-100 transition-opacity"></div>
|
||||||
|
<div class="flex items-start justify-between">
|
||||||
|
<div class="size-12 rounded-lg bg-cover bg-center shadow-lg" data-alt="ElevenLabs logo icon red background" style="background-image: url('https://lh3.googleusercontent.com/aida-public/AB6AXuBVqan6FEM01JlSNs0aKYTXdgzFAXn1xT_Yx0S3VszDGf5x0hsptwFAxMNtCEtJZOW65aZMncYKyPpzdAoIHLaBDk_ORHur89Redtuublqbd51Cr6sKVdMJ2VAAihNuVPsvpNBdIiLGuf40kXbe3HJrdYVNAKi27xcxBICheI4OzkFF7uJvChUyDbumqLRFUDMssCuxZIQYTeDKbTT628ZBFUN2H8u0RfVSPEyEhJVSE2NCAD4mH-dWGmsQzMWjmeZkwXJKpw1TGHFp');"></div>
|
||||||
|
<div class="flex items-center justify-center size-8 rounded-full bg-background-dark text-gray-400 group-hover:text-primary group-hover:bg-primary/10 transition-colors">
|
||||||
|
<span class="material-symbols-outlined text-lg">arrow_outward</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 class="text-white text-lg font-bold leading-tight group-hover:text-primary transition-colors">ElevenLabs</h3>
|
||||||
|
<p class="text-gray-400 text-sm mt-2 line-clamp-2">The most realistic AI voice generator and text-to-speech software.</p>
|
||||||
|
</div>
|
||||||
|
<div class="mt-auto flex items-center justify-between pt-4 border-t border-border-dark">
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<span class="px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider bg-white/5 text-gray-300 border border-white/10">Audio</span>
|
||||||
|
<span class="px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider bg-white/5 text-gray-300 border border-white/10">TTS</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-1 text-gray-500 text-xs">
|
||||||
|
<span class="material-symbols-outlined text-[14px]">visibility</span>
|
||||||
|
<span>210k</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Card 7 -->
|
||||||
|
<div class="group relative flex flex-col gap-4 rounded-xl border border-border-dark bg-surface-dark p-5 card-hover transition-all duration-300 cursor-pointer overflow-hidden">
|
||||||
|
<div class="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-primary to-secondary opacity-0 group-hover:opacity-100 transition-opacity"></div>
|
||||||
|
<div class="flex items-start justify-between">
|
||||||
|
<div class="size-12 rounded-lg bg-cover bg-center shadow-lg" data-alt="Stable Diffusion logo icon teal background" style="background-image: url('https://lh3.googleusercontent.com/aida-public/AB6AXuB4zzlwlZK6dJVjV4vvFGvRmtsvd7dvQMCC1mAzFfEqA2sYnJnEdTDAF76lfVYkaKzibwuppHRIcEgbT8xaoWOBF1qdMiV3Qs3P9fEOLJ2jApPPqJyBnPXsvxFsI6FTlZ0SS_Rpblvjln_n_YLh8Wi_VWh8lCJirMM5vMCuyrNGK9Crxv3bnJzlbH34i9vmxSxSQ5uCmoAE5GPxkD23vzun9BBOKjRK-Ln7DhQ_bGhnVxkxEtM8Z-DVuGg2dNE7WnD7BEBXTOBh5XQa');"></div>
|
||||||
|
<div class="flex items-center justify-center size-8 rounded-full bg-background-dark text-gray-400 group-hover:text-primary group-hover:bg-primary/10 transition-colors">
|
||||||
|
<span class="material-symbols-outlined text-lg">arrow_outward</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 class="text-white text-lg font-bold leading-tight group-hover:text-primary transition-colors">Stable Diffusion</h3>
|
||||||
|
<p class="text-gray-400 text-sm mt-2 line-clamp-2">Open source latent text-to-image diffusion model for image generation.</p>
|
||||||
|
</div>
|
||||||
|
<div class="mt-auto flex items-center justify-between pt-4 border-t border-border-dark">
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<span class="px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider bg-white/5 text-gray-300 border border-white/10">Open Source</span>
|
||||||
|
<span class="px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider bg-white/5 text-gray-300 border border-white/10">Image</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-1 text-gray-500 text-xs">
|
||||||
|
<span class="material-symbols-outlined text-[14px]">visibility</span>
|
||||||
|
<span>950k</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Card 8 -->
|
||||||
|
<div class="group relative flex flex-col gap-4 rounded-xl border border-border-dark bg-surface-dark p-5 card-hover transition-all duration-300 cursor-pointer overflow-hidden">
|
||||||
|
<div class="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-primary to-secondary opacity-0 group-hover:opacity-100 transition-opacity"></div>
|
||||||
|
<div class="flex items-start justify-between">
|
||||||
|
<div class="size-12 rounded-lg bg-cover bg-center shadow-lg" data-alt="Notion AI logo icon indigo background" style="background-image: url('https://lh3.googleusercontent.com/aida-public/AB6AXuAgnpQqxiQhRd6xj43Q0hY2EQTna5khOzvl_NxO3OJN_7_nsfC0wP1x7ZmfBEvEEOoGi7NHVx6pvOpJb7Zk7dvUQWD64Y7Nrhc0mi0wsVIB1U8OfG1wioEusBqkI6zJQQh8W0om8gi2ubnJn2McHrvANulIAVnRxn1zPcy-SAnW1rLynAdVFKurBX5qK3BFBi_knAMoL8bTL_1LTxIdhdCggw6WeCGsFgUu6vqwjq9prGC-j94Fr8GibbjDSABY36P1OoZg59Aiv1M4');"></div>
|
||||||
|
<div class="flex items-center justify-center size-8 rounded-full bg-background-dark text-gray-400 group-hover:text-primary group-hover:bg-primary/10 transition-colors">
|
||||||
|
<span class="material-symbols-outlined text-lg">arrow_outward</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 class="text-white text-lg font-bold leading-tight group-hover:text-primary transition-colors">Notion AI</h3>
|
||||||
|
<p class="text-gray-400 text-sm mt-2 line-clamp-2">Access the limitless power of AI, right inside your Notion workspace.</p>
|
||||||
|
</div>
|
||||||
|
<div class="mt-auto flex items-center justify-between pt-4 border-t border-border-dark">
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<span class="px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider bg-white/5 text-gray-300 border border-white/10">Productivity</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-1 text-gray-500 text-xs">
|
||||||
|
<span class="material-symbols-outlined text-[14px]">visibility</span>
|
||||||
|
<span>600k</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Pagination -->
|
||||||
|
<div class="flex items-center justify-center py-4">
|
||||||
|
<nav class="flex items-center gap-2">
|
||||||
|
<a class="flex size-10 items-center justify-center rounded-full border border-border-dark text-gray-400 hover:text-white hover:border-primary transition-colors" href="#">
|
||||||
|
<span class="material-symbols-outlined text-base">chevron_left</span>
|
||||||
|
</a>
|
||||||
|
<a class="flex size-10 items-center justify-center rounded-full bg-primary text-background-dark text-sm font-bold shadow-[0_0_10px_rgba(37,192,244,0.3)]" href="#">1</a>
|
||||||
|
<a class="flex size-10 items-center justify-center rounded-full border border-transparent hover:bg-surface-dark text-gray-400 hover:text-white text-sm font-medium transition-colors" href="#">2</a>
|
||||||
|
<a class="flex size-10 items-center justify-center rounded-full border border-transparent hover:bg-surface-dark text-gray-400 hover:text-white text-sm font-medium transition-colors" href="#">3</a>
|
||||||
|
<span class="flex size-10 items-center justify-center text-gray-600">...</span>
|
||||||
|
<a class="flex size-10 items-center justify-center rounded-full border border-transparent hover:bg-surface-dark text-gray-400 hover:text-white text-sm font-medium transition-colors" href="#">12</a>
|
||||||
|
<a class="flex size-10 items-center justify-center rounded-full border border-border-dark text-gray-400 hover:text-white hover:border-primary transition-colors" href="#">
|
||||||
|
<span class="material-symbols-outlined text-base">chevron_right</span>
|
||||||
|
</a>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
<!-- Simple Footer -->
|
||||||
|
<footer class="w-full border-t border-border-dark bg-background-dark py-8 mt-auto">
|
||||||
|
<div class="mx-auto max-w-[1200px] flex flex-col md:flex-row items-center justify-between gap-6 px-6 lg:px-10">
|
||||||
|
<div class="flex items-center gap-2 text-gray-400 text-sm">
|
||||||
|
<span>© 2023 ZJPB AI Directory. All rights reserved.</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-6">
|
||||||
|
<a class="text-gray-500 hover:text-primary transition-colors" href="#">
|
||||||
|
<i class="fab fa-twitter text-lg"></i> <!-- Placeholder for social icon -->
|
||||||
|
<span class="text-sm">Twitter</span>
|
||||||
|
</a>
|
||||||
|
<a class="text-gray-500 hover:text-primary transition-colors" href="#">
|
||||||
|
<span class="text-sm">Discord</span>
|
||||||
|
</a>
|
||||||
|
<a class="text-gray-500 hover:text-primary transition-colors" href="#">
|
||||||
|
<span class="text-sm">Privacy Policy</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</body></html>
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 354 KiB |
17
templates/admin/custom_base.html
Normal file
17
templates/admin/custom_base.html
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{% extends 'admin/base.html' %}
|
||||||
|
|
||||||
|
{% block head_css %}
|
||||||
|
{{ super() }}
|
||||||
|
<!-- Google Fonts -->
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&family=Noto+Sans:wght@400;500;700&display=swap" rel="stylesheet">
|
||||||
|
<!-- Custom Admin Theme -->
|
||||||
|
<link href="{{ url_for('static', filename='css/admin-theme.css') }}" rel="stylesheet">
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
<body class="admin-theme">
|
||||||
|
{{ super() }}
|
||||||
|
</body>
|
||||||
|
{% endblock %}
|
||||||
124
templates/admin/site/create.html
Normal file
124
templates/admin/site/create.html
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
{% extends 'admin/model/create.html' %}
|
||||||
|
|
||||||
|
{% block tail %}
|
||||||
|
{{ super() }}
|
||||||
|
<style>
|
||||||
|
.auto-fetch-btn {
|
||||||
|
margin-top: 10px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
.fetch-status {
|
||||||
|
margin-top: 10px;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 8px;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.fetch-status.success {
|
||||||
|
background-color: rgba(34, 197, 94, 0.1);
|
||||||
|
border: 1px solid rgba(34, 197, 94, 0.3);
|
||||||
|
color: #4ade80;
|
||||||
|
}
|
||||||
|
.fetch-status.error {
|
||||||
|
background-color: rgba(239, 68, 68, 0.1);
|
||||||
|
border: 1px solid rgba(239, 68, 68, 0.3);
|
||||||
|
color: #f87171;
|
||||||
|
}
|
||||||
|
.auto-fetch-btn .loading-icon {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.auto-fetch-btn.loading .loading-icon {
|
||||||
|
display: inline-block;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
.auto-fetch-btn.loading .normal-icon {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
@keyframes spin {
|
||||||
|
from { transform: rotate(0deg); }
|
||||||
|
to { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
// 在URL字段后添加"自动获取"按钮
|
||||||
|
const urlField = document.querySelector('input[name="url"]');
|
||||||
|
if (urlField) {
|
||||||
|
const fetchBtn = document.createElement('button');
|
||||||
|
fetchBtn.type = 'button';
|
||||||
|
fetchBtn.className = 'btn btn-info auto-fetch-btn';
|
||||||
|
fetchBtn.innerHTML = '<span class="normal-icon">↻</span><span class="loading-icon">↻</span> 自动获取网站信息';
|
||||||
|
|
||||||
|
const statusDiv = document.createElement('div');
|
||||||
|
statusDiv.className = 'fetch-status';
|
||||||
|
|
||||||
|
urlField.parentNode.appendChild(fetchBtn);
|
||||||
|
urlField.parentNode.appendChild(statusDiv);
|
||||||
|
|
||||||
|
fetchBtn.addEventListener('click', function() {
|
||||||
|
const url = urlField.value.trim();
|
||||||
|
|
||||||
|
if (!url) {
|
||||||
|
showStatus('请先输入网站URL', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示加载状态
|
||||||
|
fetchBtn.disabled = true;
|
||||||
|
fetchBtn.classList.add('loading');
|
||||||
|
statusDiv.style.display = 'none';
|
||||||
|
|
||||||
|
// 调用API获取网站信息
|
||||||
|
fetch('/api/fetch-website-info', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ url: url })
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
// 自动填充表单字段
|
||||||
|
const nameField = document.querySelector('input[name="name"]');
|
||||||
|
const shortDescField = document.querySelector('input[name="short_desc"]');
|
||||||
|
const descriptionField = document.querySelector('textarea[name="description"]');
|
||||||
|
const logoField = document.querySelector('input[name="logo"]');
|
||||||
|
|
||||||
|
if (nameField && data.data.name) {
|
||||||
|
nameField.value = data.data.name;
|
||||||
|
}
|
||||||
|
if (shortDescField && data.data.description) {
|
||||||
|
shortDescField.value = data.data.description.substring(0, 100);
|
||||||
|
}
|
||||||
|
if (descriptionField && data.data.description) {
|
||||||
|
descriptionField.value = data.data.description;
|
||||||
|
}
|
||||||
|
if (logoField && data.data.logo) {
|
||||||
|
logoField.value = data.data.logo;
|
||||||
|
}
|
||||||
|
|
||||||
|
showStatus('✓ 网站信息获取成功!已自动填充表单', 'success');
|
||||||
|
} else {
|
||||||
|
showStatus('✗ ' + (data.message || '获取失败,请手动填写'), 'error');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error:', error);
|
||||||
|
showStatus('✗ 网络请求失败,请手动填写', 'error');
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
fetchBtn.disabled = false;
|
||||||
|
fetchBtn.classList.remove('loading');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function showStatus(message, type) {
|
||||||
|
statusDiv.textContent = message;
|
||||||
|
statusDiv.className = 'fetch-status ' + type;
|
||||||
|
statusDiv.style.display = 'block';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
124
templates/admin/site/edit.html
Normal file
124
templates/admin/site/edit.html
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
{% extends 'admin/model/edit.html' %}
|
||||||
|
|
||||||
|
{% block tail %}
|
||||||
|
{{ super() }}
|
||||||
|
<style>
|
||||||
|
.auto-fetch-btn {
|
||||||
|
margin-top: 10px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
.fetch-status {
|
||||||
|
margin-top: 10px;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 8px;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.fetch-status.success {
|
||||||
|
background-color: rgba(34, 197, 94, 0.1);
|
||||||
|
border: 1px solid rgba(34, 197, 94, 0.3);
|
||||||
|
color: #4ade80;
|
||||||
|
}
|
||||||
|
.fetch-status.error {
|
||||||
|
background-color: rgba(239, 68, 68, 0.1);
|
||||||
|
border: 1px solid rgba(239, 68, 68, 0.3);
|
||||||
|
color: #f87171;
|
||||||
|
}
|
||||||
|
.auto-fetch-btn .loading-icon {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.auto-fetch-btn.loading .loading-icon {
|
||||||
|
display: inline-block;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
.auto-fetch-btn.loading .normal-icon {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
@keyframes spin {
|
||||||
|
from { transform: rotate(0deg); }
|
||||||
|
to { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
// 在URL字段后添加"自动获取"按钮
|
||||||
|
const urlField = document.querySelector('input[name="url"]');
|
||||||
|
if (urlField) {
|
||||||
|
const fetchBtn = document.createElement('button');
|
||||||
|
fetchBtn.type = 'button';
|
||||||
|
fetchBtn.className = 'btn btn-info auto-fetch-btn';
|
||||||
|
fetchBtn.innerHTML = '<span class="normal-icon">↻</span><span class="loading-icon">↻</span> 自动获取网站信息';
|
||||||
|
|
||||||
|
const statusDiv = document.createElement('div');
|
||||||
|
statusDiv.className = 'fetch-status';
|
||||||
|
|
||||||
|
urlField.parentNode.appendChild(fetchBtn);
|
||||||
|
urlField.parentNode.appendChild(statusDiv);
|
||||||
|
|
||||||
|
fetchBtn.addEventListener('click', function() {
|
||||||
|
const url = urlField.value.trim();
|
||||||
|
|
||||||
|
if (!url) {
|
||||||
|
showStatus('请先输入网站URL', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示加载状态
|
||||||
|
fetchBtn.disabled = true;
|
||||||
|
fetchBtn.classList.add('loading');
|
||||||
|
statusDiv.style.display = 'none';
|
||||||
|
|
||||||
|
// 调用API获取网站信息
|
||||||
|
fetch('/api/fetch-website-info', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ url: url })
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
// 自动填充表单字段
|
||||||
|
const nameField = document.querySelector('input[name="name"]');
|
||||||
|
const shortDescField = document.querySelector('input[name="short_desc"]');
|
||||||
|
const descriptionField = document.querySelector('textarea[name="description"]');
|
||||||
|
const logoField = document.querySelector('input[name="logo"]');
|
||||||
|
|
||||||
|
if (nameField && data.data.name) {
|
||||||
|
nameField.value = data.data.name;
|
||||||
|
}
|
||||||
|
if (shortDescField && data.data.description) {
|
||||||
|
shortDescField.value = data.data.description.substring(0, 100);
|
||||||
|
}
|
||||||
|
if (descriptionField && data.data.description) {
|
||||||
|
descriptionField.value = data.data.description;
|
||||||
|
}
|
||||||
|
if (logoField && data.data.logo) {
|
||||||
|
logoField.value = data.data.logo;
|
||||||
|
}
|
||||||
|
|
||||||
|
showStatus('✓ 网站信息获取成功!已自动填充表单', 'success');
|
||||||
|
} else {
|
||||||
|
showStatus('✗ ' + (data.message || '获取失败,请手动填写'), 'error');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error:', error);
|
||||||
|
showStatus('✗ 网络请求失败,请手动填写', 'error');
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
fetchBtn.disabled = false;
|
||||||
|
fetchBtn.classList.remove('loading');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function showStatus(message, type) {
|
||||||
|
statusDiv.textContent = message;
|
||||||
|
statusDiv.className = 'fetch-status ' + type;
|
||||||
|
statusDiv.style.display = 'block';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
121
templates/admin_login.html
Normal file
121
templates/admin_login.html
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}管理员登录 - ZJPB 焦提示词{% endblock %}
|
||||||
|
|
||||||
|
{% block extra_css %}
|
||||||
|
<style>
|
||||||
|
.glass-panel {
|
||||||
|
background: rgba(22, 33, 37, 0.7);
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
|
-webkit-backdrop-filter: blur(12px);
|
||||||
|
}
|
||||||
|
.tech-bg-grid {
|
||||||
|
background-image: radial-gradient(circle at center, rgba(37, 192, 244, 0.05) 0%, transparent 70%),
|
||||||
|
linear-gradient(rgba(37, 192, 244, 0.03) 1px, transparent 1px),
|
||||||
|
linear-gradient(90deg, rgba(37, 192, 244, 0.03) 1px, transparent 1px);
|
||||||
|
background-size: 100% 100%, 40px 40px, 40px 40px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<!-- Override default layout -->
|
||||||
|
</div> <!-- Close main wrapper -->
|
||||||
|
|
||||||
|
<body class="bg-background-dark font-display text-white min-h-screen flex flex-col items-center justify-center relative overflow-hidden selection:bg-primary/30">
|
||||||
|
<!-- Ambient Background -->
|
||||||
|
<div class="absolute inset-0 z-0 tech-bg-grid pointer-events-none"></div>
|
||||||
|
<div class="absolute top-[-20%] right-[-10%] w-[600px] h-[600px] bg-primary/10 rounded-full blur-[120px] pointer-events-none"></div>
|
||||||
|
<div class="absolute bottom-[-10%] left-[-10%] w-[500px] h-[500px] bg-purple-600/10 rounded-full blur-[100px] pointer-events-none"></div>
|
||||||
|
|
||||||
|
<!-- Main Content Wrapper -->
|
||||||
|
<div class="w-full max-w-[480px] px-4 z-10 flex flex-col gap-6">
|
||||||
|
<!-- Back Navigation -->
|
||||||
|
<a class="group flex items-center gap-2 text-gray-400 hover:text-white transition-colors w-fit" href="{{ url_for('index') }}">
|
||||||
|
<div class="flex items-center justify-center w-8 h-8 rounded-full border border-border-dark bg-surface-dark group-hover:border-primary/50 transition-colors">
|
||||||
|
<span class="material-symbols-outlined text-sm">arrow_back</span>
|
||||||
|
</div>
|
||||||
|
<span class="text-sm font-medium">返回首页</span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<!-- Login Card -->
|
||||||
|
<div class="glass-panel border border-white/5 dark:border-border-dark/50 rounded-xl shadow-2xl p-8 md:p-10 relative overflow-hidden">
|
||||||
|
<!-- Decorative Accent -->
|
||||||
|
<div class="absolute top-0 left-0 w-full h-[2px] bg-gradient-to-r from-transparent via-primary to-transparent opacity-70"></div>
|
||||||
|
|
||||||
|
<!-- Header -->
|
||||||
|
<div class="flex flex-col gap-2 mb-8">
|
||||||
|
<div class="flex items-center gap-3 mb-2">
|
||||||
|
<div class="p-2 rounded-lg bg-primary/10 text-primary">
|
||||||
|
<span class="material-symbols-outlined text-2xl">shield_person</span>
|
||||||
|
</div>
|
||||||
|
<span class="text-xs font-bold tracking-widest uppercase text-primary/80">System Access</span>
|
||||||
|
</div>
|
||||||
|
<h1 class="text-3xl font-black tracking-tight text-white leading-tight">管理员登录</h1>
|
||||||
|
<p class="text-gray-400 text-base font-normal">输入您的登录凭据以访问后台管理系统</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Error Messages -->
|
||||||
|
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||||
|
{% if messages %}
|
||||||
|
{% for category, message in messages %}
|
||||||
|
<div class="mb-6 p-3 rounded-lg bg-red-500/10 border border-red-500/20 flex items-start gap-3">
|
||||||
|
<span class="material-symbols-outlined text-red-400 text-sm mt-0.5">error</span>
|
||||||
|
<p class="text-red-400 text-sm">{{ message }}</p>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
|
|
||||||
|
<!-- Form -->
|
||||||
|
<form method="POST" action="{{ url_for('admin_login') }}" class="flex flex-col gap-5">
|
||||||
|
<!-- Username Field -->
|
||||||
|
<div class="space-y-2">
|
||||||
|
<label class="text-white text-sm font-medium leading-normal" for="username">用户名</label>
|
||||||
|
<div class="relative group">
|
||||||
|
<input class="form-input flex w-full h-14 pl-12 pr-4 rounded-lg text-white focus:outline-0 focus:ring-0 border border-border-dark bg-surface-dark focus:border-primary placeholder:text-gray-500 text-base font-normal leading-normal transition-colors"
|
||||||
|
id="username"
|
||||||
|
name="username"
|
||||||
|
placeholder="输入您的用户名或邮箱"
|
||||||
|
type="text"
|
||||||
|
required
|
||||||
|
autofocus/>
|
||||||
|
<div class="absolute left-4 top-1/2 -translate-y-1/2 text-gray-400 group-focus-within:text-primary transition-colors flex items-center justify-center pointer-events-none">
|
||||||
|
<span class="material-symbols-outlined text-[20px]">person</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Password Field -->
|
||||||
|
<div class="space-y-2">
|
||||||
|
<label class="text-white text-sm font-medium leading-normal" for="password">密码</label>
|
||||||
|
<div class="flex w-full items-stretch rounded-lg group relative">
|
||||||
|
<input class="form-input flex w-full h-14 pl-12 pr-12 rounded-lg text-white focus:outline-0 focus:ring-0 border border-border-dark bg-surface-dark focus:border-primary placeholder:text-gray-500 text-base font-normal leading-normal transition-colors z-10"
|
||||||
|
id="password"
|
||||||
|
name="password"
|
||||||
|
placeholder="输入您的密码"
|
||||||
|
type="password"
|
||||||
|
required/>
|
||||||
|
<div class="absolute left-4 top-1/2 -translate-y-1/2 text-gray-400 group-focus-within:text-primary transition-colors flex items-center justify-center pointer-events-none z-20">
|
||||||
|
<span class="material-symbols-outlined text-[20px]">lock</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Login Button -->
|
||||||
|
<button class="mt-4 flex w-full h-12 cursor-pointer items-center justify-center overflow-hidden rounded-lg bg-primary text-black text-base font-bold leading-normal tracking-[0.015em] hover:bg-primary/90 transition-all active:scale-[0.98] shadow-[0_0_20px_rgba(37,192,244,0.3)] hover:shadow-[0_0_30px_rgba(37,192,244,0.5)]"
|
||||||
|
type="submit">
|
||||||
|
<span class="truncate">登录</span>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<!-- Footer Meta -->
|
||||||
|
<div class="mt-8 flex justify-center gap-6 border-t border-border-dark/30 pt-6">
|
||||||
|
<p class="text-gray-400 text-xs text-center">
|
||||||
|
ZJPB - 焦提示词 管理系统
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
{% endblock %}
|
||||||
125
templates/base.html
Normal file
125
templates/base.html
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html class="dark" lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>{% block title %}ZJPB - 焦提示词{% endblock %}</title>
|
||||||
|
<meta name="description" content="{% block description %}焦提示词 - 精选AI工具和产品导航,发现最新最好用的人工智能应用{% endblock %}">
|
||||||
|
<meta name="keywords" content="{% block keywords %}ZJPB,焦提示词,AI工具,人工智能,ChatGPT,AI导航{% endblock %}">
|
||||||
|
|
||||||
|
<!-- Google Fonts -->
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&family=Noto+Sans:wght@400;500;700&display=swap" rel="stylesheet">
|
||||||
|
|
||||||
|
<!-- Material Symbols -->
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet">
|
||||||
|
|
||||||
|
<!-- Tailwind CSS -->
|
||||||
|
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
||||||
|
<script>
|
||||||
|
tailwind.config = {
|
||||||
|
darkMode: "class",
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
colors: {
|
||||||
|
primary: "#25c0f4",
|
||||||
|
secondary: "#7c3aed",
|
||||||
|
"background-light": "#f5f8f8",
|
||||||
|
"background-dark": "#111618",
|
||||||
|
"surface-dark": "#1b2427",
|
||||||
|
"border-dark": "#283539",
|
||||||
|
},
|
||||||
|
fontFamily: {
|
||||||
|
"display": ["Space Grotesk", "sans-serif"],
|
||||||
|
"body": ["Noto Sans", "sans-serif"],
|
||||||
|
},
|
||||||
|
backgroundImage: {
|
||||||
|
'gradient-tech': 'linear-gradient(135deg, #25c0f4 0%, #7c3aed 100%)',
|
||||||
|
'gradient-text': 'linear-gradient(to right, #25c0f4, #a855f7)',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: 'Space Grotesk', sans-serif;
|
||||||
|
}
|
||||||
|
.text-gradient {
|
||||||
|
background: linear-gradient(to right, #25c0f4, #c084fc);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
}
|
||||||
|
.card-hover:hover {
|
||||||
|
transform: translateY(-4px);
|
||||||
|
box-shadow: 0 10px 30px -10px rgba(37, 192, 244, 0.15);
|
||||||
|
border-color: #25c0f4;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% block extra_css %}{% endblock %}
|
||||||
|
</head>
|
||||||
|
<body class="bg-background-light dark:bg-background-dark text-slate-900 dark:text-white antialiased selection:bg-primary selection:text-black">
|
||||||
|
<div class="relative flex min-h-screen w-full flex-col overflow-x-hidden">
|
||||||
|
<!-- Background Gradient Elements -->
|
||||||
|
<div class="fixed top-0 left-0 w-full h-96 bg-primary/5 blur-[120px] rounded-full pointer-events-none -translate-y-1/2"></div>
|
||||||
|
<div class="fixed top-20 right-0 w-96 h-96 bg-secondary/10 blur-[100px] rounded-full pointer-events-none translate-x-1/3"></div>
|
||||||
|
|
||||||
|
<!-- Top Navigation -->
|
||||||
|
<header class="sticky top-0 z-50 w-full border-b border-solid border-border-dark bg-background-dark/80 backdrop-blur-md">
|
||||||
|
<div class="mx-auto flex h-16 max-w-[1440px] items-center justify-between px-6 lg:px-10">
|
||||||
|
<div class="flex items-center gap-8">
|
||||||
|
<!-- Logo -->
|
||||||
|
<a class="flex items-center gap-3 text-white hover:opacity-90 transition-opacity" href="{{ url_for('index') }}">
|
||||||
|
<div class="flex items-center justify-center size-8 rounded-lg bg-gradient-tech text-black">
|
||||||
|
<span class="material-symbols-outlined" style="font-size: 20px; font-weight: 700;">bolt</span>
|
||||||
|
</div>
|
||||||
|
<h2 class="text-white text-lg font-bold leading-tight tracking-tight">ZJPB</h2>
|
||||||
|
</a>
|
||||||
|
<!-- Desktop Nav -->
|
||||||
|
<nav class="hidden md:flex items-center gap-8">
|
||||||
|
<a class="text-gray-300 hover:text-primary text-sm font-medium transition-colors" href="{{ url_for('index') }}">首页</a>
|
||||||
|
{% if current_user.is_authenticated %}
|
||||||
|
<a class="text-gray-300 hover:text-primary text-sm font-medium transition-colors" href="{{ url_for('admin.index') }}">后台管理</a>
|
||||||
|
{% endif %}
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Actions -->
|
||||||
|
<div class="flex items-center gap-4">
|
||||||
|
{% if current_user.is_authenticated %}
|
||||||
|
<button onclick="window.location.href='{{ url_for('admin_logout') }}'" class="hidden sm:flex h-9 px-4 items-center justify-center rounded-lg text-sm font-bold text-white hover:bg-white/5 transition-colors">
|
||||||
|
退出
|
||||||
|
</button>
|
||||||
|
{% else %}
|
||||||
|
<button onclick="window.location.href='{{ url_for('admin_login') }}'" class="hidden sm:flex h-9 px-4 items-center justify-center rounded-lg text-sm font-bold text-white hover:bg-white/5 transition-colors">
|
||||||
|
登录
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<!-- Main Content -->
|
||||||
|
<main class="flex-1">
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
<footer class="w-full border-t border-border-dark bg-background-dark py-8 mt-auto">
|
||||||
|
<div class="mx-auto max-w-[1200px] flex flex-col md:flex-row items-center justify-between gap-6 px-6 lg:px-10">
|
||||||
|
<div class="flex items-center gap-2 text-gray-400 text-sm">
|
||||||
|
<span>© 2024 ZJPB - 焦提示词. All rights reserved.</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-6">
|
||||||
|
<a class="text-gray-500 hover:text-primary transition-colors text-sm" href="#">
|
||||||
|
发现最好用的AI工具
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% block extra_js %}{% endblock %}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
136
templates/detail.html
Normal file
136
templates/detail.html
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}{{ site.name }} - ZJPB 焦提示词{% endblock %}
|
||||||
|
{% block description %}{{ site.short_desc or site.description[:150] }}{% endblock %}
|
||||||
|
{% block keywords %}{{ site.name }},{% for tag in site.tags %}{{ tag.name }},{% endfor %}ZJPB,焦提示词,AI工具{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<main class="flex-grow w-full max-w-[1280px] mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||||
|
<!-- Back Button -->
|
||||||
|
<div class="mb-8">
|
||||||
|
<a href="{{ url_for('index') }}" class="group flex items-center gap-2 text-gray-400 hover:text-primary transition-colors text-sm font-bold">
|
||||||
|
<span class="material-symbols-outlined text-[20px] group-hover:-translate-x-1 transition-transform">arrow_back</span>
|
||||||
|
<span>返回首页</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 lg:grid-cols-12 gap-8 lg:gap-12">
|
||||||
|
<!-- Left Column: Main Content -->
|
||||||
|
<div class="lg:col-span-8 flex flex-col gap-8">
|
||||||
|
<!-- Header Section -->
|
||||||
|
<div class="flex flex-col sm:flex-row gap-6 items-start">
|
||||||
|
<!-- Logo Container -->
|
||||||
|
<div class="relative shrink-0">
|
||||||
|
<div class="absolute -inset-1 bg-gradient-to-br from-primary via-purple-500 to-secondary rounded-2xl opacity-30 blur-md"></div>
|
||||||
|
{% if site.logo %}
|
||||||
|
<div class="relative size-[120px] rounded-xl bg-surface-dark flex items-center justify-center overflow-hidden border border-border-dark" style="background-image: url('{{ site.logo }}'); background-size: cover; background-position: center;"></div>
|
||||||
|
{% else %}
|
||||||
|
<div class="relative size-[120px] rounded-xl bg-gradient-to-br from-primary to-secondary flex items-center justify-center border border-border-dark">
|
||||||
|
<span class="text-5xl font-bold text-black">{{ site.name[0] }}</span>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Title & Meta -->
|
||||||
|
<div class="flex flex-col gap-3 flex-1">
|
||||||
|
<div>
|
||||||
|
<h1 class="text-4xl font-bold text-white tracking-tight font-display mb-1">{{ site.name }}</h1>
|
||||||
|
<a class="text-primary hover:text-primary-dark hover:underline text-base font-medium flex items-center gap-1" href="{{ site.url }}" target="_blank">
|
||||||
|
{{ site.url }}
|
||||||
|
<span class="material-symbols-outlined text-[16px]">open_in_new</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Tags -->
|
||||||
|
<div class="flex flex-wrap gap-2">
|
||||||
|
{% for tag in site.tags %}
|
||||||
|
<span class="inline-flex items-center px-3 py-1 rounded-full text-xs font-medium bg-surface-dark text-gray-300 border border-border-dark hover:border-primary/30 transition-colors cursor-default">
|
||||||
|
{{ tag.name }}
|
||||||
|
</span>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Stats Row -->
|
||||||
|
<div class="flex items-center gap-6 mt-1 text-gray-400 text-sm">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span class="material-symbols-outlined text-[18px]">visibility</span>
|
||||||
|
<span>{{ site.view_count }} 次浏览</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span class="material-symbols-outlined text-[18px]">calendar_today</span>
|
||||||
|
<span>{{ site.created_at.strftime('%Y年%m月%d日') }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr class="border-border-dark/60"/>
|
||||||
|
|
||||||
|
<!-- Main Content Body -->
|
||||||
|
<div class="space-y-8">
|
||||||
|
<!-- Overview -->
|
||||||
|
{% if site.short_desc %}
|
||||||
|
<section>
|
||||||
|
<h3 class="text-xl font-bold text-white mb-4 flex items-center gap-2">
|
||||||
|
<span class="material-symbols-outlined text-primary">info</span>
|
||||||
|
产品简介
|
||||||
|
</h3>
|
||||||
|
<p class="text-gray-400 leading-relaxed text-lg">
|
||||||
|
{{ site.short_desc }}
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<!-- Detailed Description -->
|
||||||
|
{% if site.description %}
|
||||||
|
<section>
|
||||||
|
<h3 class="text-xl font-bold text-white mb-4 flex items-center gap-2">
|
||||||
|
<span class="material-symbols-outlined text-primary">article</span>
|
||||||
|
详细介绍
|
||||||
|
</h3>
|
||||||
|
<div class="text-gray-400 space-y-4 leading-relaxed whitespace-pre-wrap">
|
||||||
|
{{ site.description|safe }}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<!-- Features -->
|
||||||
|
{% if site.features %}
|
||||||
|
<section>
|
||||||
|
<h3 class="text-xl font-bold text-white mb-4 flex items-center gap-2">
|
||||||
|
<span class="material-symbols-outlined text-primary">star</span>
|
||||||
|
主要功能
|
||||||
|
</h3>
|
||||||
|
<div class="text-gray-400 space-y-4 leading-relaxed whitespace-pre-wrap">
|
||||||
|
{{ site.features|safe }}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Right Column: Sidebar -->
|
||||||
|
<div class="lg:col-span-4 space-y-6">
|
||||||
|
<!-- CTA Card -->
|
||||||
|
<div class="rounded-2xl p-6 flex flex-col gap-4 shadow-xl shadow-black/20 sticky top-24 bg-surface-dark/60 backdrop-blur-xl border border-border-dark">
|
||||||
|
<div class="flex items-center justify-between mb-2">
|
||||||
|
<span class="text-white font-bold text-lg">立即体验</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a class="group relative flex items-center justify-center w-full overflow-hidden rounded-xl bg-primary hover:bg-primary/90 p-4 focus:outline-none transition-all hover:scale-[1.02] shadow-[0_0_20px_rgba(37,192,244,0.3)] hover:shadow-[0_0_30px_rgba(37,192,244,0.5)]" href="{{ site.url }}" target="_blank">
|
||||||
|
<span class="flex items-center gap-2 text-black font-bold text-base uppercase tracking-wide">
|
||||||
|
访问网站
|
||||||
|
<span class="material-symbols-outlined text-[20px] font-bold">arrow_outward</span>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div class="pt-4 border-t border-border-dark">
|
||||||
|
<p class="text-gray-500 text-xs text-center">
|
||||||
|
{{ site.url }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
{% endblock %}
|
||||||
82
templates/index.html
Normal file
82
templates/index.html
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}ZJPB - 焦提示词 - 发现最好用的AI产品{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<main class="flex-1 flex flex-col items-center w-full px-6 lg:px-10 py-8">
|
||||||
|
<div class="w-full max-w-[1200px] flex flex-col gap-10">
|
||||||
|
<!-- Hero Section -->
|
||||||
|
<div class="flex flex-col md:flex-row items-start md:items-end justify-between gap-6 pb-6 border-b border-border-dark/50">
|
||||||
|
<div class="flex flex-col gap-2 max-w-2xl">
|
||||||
|
<h1 class="text-4xl md:text-5xl lg:text-6xl font-black tracking-tighter text-white mb-2">
|
||||||
|
ZJPB - <span class="text-gradient">焦提示词</span>
|
||||||
|
</h1>
|
||||||
|
<p class="text-gray-400 text-lg md:text-xl font-light">
|
||||||
|
发现最新最好用的AI工具和产品
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Filter Chips -->
|
||||||
|
<div class="w-full overflow-x-auto pb-2 scrollbar-hide">
|
||||||
|
<div class="flex gap-3 min-w-max">
|
||||||
|
<a href="{{ url_for('index') }}" class="flex items-center h-9 px-5 rounded-full {% if not selected_tag %}bg-primary text-background-dark shadow-[0_0_10px_rgba(37,192,244,0.4)]{% else %}bg-surface-dark border border-border-dark text-gray-300 hover:text-white hover:border-gray-500{% endif %} font-bold text-sm transition-all">
|
||||||
|
全部
|
||||||
|
</a>
|
||||||
|
{% for tag in tags %}
|
||||||
|
<a href="{{ url_for('index', tag=tag.slug) }}" class="flex items-center gap-2 h-9 px-5 rounded-full {% if selected_tag and selected_tag.id == tag.id %}bg-primary text-background-dark shadow-[0_0_10px_rgba(37,192,244,0.4)]{% else %}bg-surface-dark border border-border-dark text-gray-300 hover:text-white hover:border-gray-500{% endif %} font-medium text-sm transition-all">
|
||||||
|
{% if tag.icon %}<span class="material-symbols-outlined text-sm">{{ tag.icon.replace('fas fa-', '').replace('fab fa-', '') }}</span>{% endif %}
|
||||||
|
{{ tag.name }}
|
||||||
|
</a>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Tool Grid -->
|
||||||
|
{% if sites %}
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
|
||||||
|
{% for site in sites %}
|
||||||
|
<div class="group relative flex flex-col gap-4 rounded-xl border border-border-dark bg-surface-dark p-5 card-hover transition-all duration-300 cursor-pointer overflow-hidden" onclick="window.location.href='{{ url_for('site_detail', slug=site.slug) }}'">
|
||||||
|
<div class="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-primary to-secondary opacity-0 group-hover:opacity-100 transition-opacity"></div>
|
||||||
|
|
||||||
|
<div class="flex items-start justify-between">
|
||||||
|
{% if site.logo %}
|
||||||
|
<div class="size-12 rounded-lg bg-cover bg-center shadow-lg" style="background-image: url('{{ site.logo }}');"></div>
|
||||||
|
{% else %}
|
||||||
|
<div class="size-12 rounded-lg bg-gradient-to-br from-primary to-secondary flex items-center justify-center text-black font-bold text-xl shadow-lg">
|
||||||
|
{{ site.name[0] }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<div class="flex items-center justify-center size-8 rounded-full bg-background-dark text-gray-400 group-hover:text-primary group-hover:bg-primary/10 transition-colors">
|
||||||
|
<span class="material-symbols-outlined text-lg">arrow_outward</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h3 class="text-white text-lg font-bold leading-tight group-hover:text-primary transition-colors">{{ site.name }}</h3>
|
||||||
|
<p class="text-gray-400 text-sm mt-2 line-clamp-2">{{ site.short_desc or '暂无描述' }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-auto flex items-center justify-between pt-4 border-t border-border-dark">
|
||||||
|
<div class="flex gap-2 flex-wrap">
|
||||||
|
{% for tag in site.tags[:2] %}
|
||||||
|
<span class="px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider bg-white/5 text-gray-300 border border-white/10">{{ tag.name }}</span>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-1 text-gray-500 text-xs">
|
||||||
|
<span class="material-symbols-outlined text-[14px]">visibility</span>
|
||||||
|
<span>{{ site.view_count }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="text-center py-20">
|
||||||
|
<span class="material-symbols-outlined text-6xl text-gray-600 mb-4 block">search_off</span>
|
||||||
|
<p class="text-gray-400 text-lg">暂无相关AI工具</p>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
{% endblock %}
|
||||||
65
test_db.py
Normal file
65
test_db.py
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
测试数据库连接
|
||||||
|
"""
|
||||||
|
import sys
|
||||||
|
import pymysql
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
import os
|
||||||
|
|
||||||
|
# 设置UTF-8编码输出
|
||||||
|
if sys.platform.startswith('win'):
|
||||||
|
import io
|
||||||
|
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
|
||||||
|
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
def test_connection():
|
||||||
|
"""测试MySQL连接"""
|
||||||
|
print("正在测试数据库连接...")
|
||||||
|
print(f"主机: {os.getenv('DB_HOST')}")
|
||||||
|
print(f"端口: {os.getenv('DB_PORT')}")
|
||||||
|
print(f"用户: {os.getenv('DB_USER')}")
|
||||||
|
print(f"数据库: {os.getenv('DB_NAME')}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
connection = pymysql.connect(
|
||||||
|
host=os.getenv('DB_HOST'),
|
||||||
|
port=int(os.getenv('DB_PORT', 3306)),
|
||||||
|
user=os.getenv('DB_USER'),
|
||||||
|
password=os.getenv('DB_PASSWORD'),
|
||||||
|
database=os.getenv('DB_NAME'),
|
||||||
|
charset='utf8mb4'
|
||||||
|
)
|
||||||
|
|
||||||
|
print("\n✓ 数据库连接成功!")
|
||||||
|
|
||||||
|
# 测试查询
|
||||||
|
with connection.cursor() as cursor:
|
||||||
|
cursor.execute("SELECT VERSION()")
|
||||||
|
version = cursor.fetchone()
|
||||||
|
print(f"✓ MySQL版本: {version[0]}")
|
||||||
|
|
||||||
|
cursor.execute("SHOW TABLES")
|
||||||
|
tables = cursor.fetchall()
|
||||||
|
if tables:
|
||||||
|
print(f"✓ 现有表: {len(tables)} 个")
|
||||||
|
for table in tables:
|
||||||
|
print(f" - {table[0]}")
|
||||||
|
else:
|
||||||
|
print(" 当前数据库为空,可以运行 init_db.py 初始化")
|
||||||
|
|
||||||
|
connection.close()
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"\n✗ 数据库连接失败: {str(e)}")
|
||||||
|
print("\n请检查:")
|
||||||
|
print("1. 服务器MySQL是否开放了3306端口")
|
||||||
|
print("2. .env文件中的数据库配置是否正确")
|
||||||
|
print("3. 数据库用户是否有远程访问权限")
|
||||||
|
print("4. 服务器防火墙/安全组是否允许3306端口")
|
||||||
|
return False
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
test_connection()
|
||||||
47
test_fetch.py
Normal file
47
test_fetch.py
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
测试网站信息抓取功能
|
||||||
|
"""
|
||||||
|
import sys
|
||||||
|
if sys.platform.startswith('win'):
|
||||||
|
import io
|
||||||
|
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
|
||||||
|
|
||||||
|
from utils.website_fetcher import WebsiteFetcher
|
||||||
|
|
||||||
|
def test_fetch():
|
||||||
|
"""测试抓取百度网站信息"""
|
||||||
|
print("="*50)
|
||||||
|
print("测试网站信息抓取功能")
|
||||||
|
print("="*50)
|
||||||
|
|
||||||
|
# 创建抓取器
|
||||||
|
fetcher = WebsiteFetcher(timeout=15)
|
||||||
|
|
||||||
|
# 测试抓取百度
|
||||||
|
test_url = "https://www.baidu.com"
|
||||||
|
print(f"\n正在抓取: {test_url}")
|
||||||
|
|
||||||
|
info = fetcher.fetch_website_info(test_url)
|
||||||
|
|
||||||
|
if info:
|
||||||
|
print("\n抓取成功!")
|
||||||
|
print("-"*50)
|
||||||
|
print(f"网站名称: {info.get('name', '')}")
|
||||||
|
print(f"网站描述: {info.get('description', '')}")
|
||||||
|
print(f"Logo URL: {info.get('logo_url', '')}")
|
||||||
|
print("-"*50)
|
||||||
|
|
||||||
|
# 测试下载Logo
|
||||||
|
if info.get('logo_url'):
|
||||||
|
print(f"\n正在下载Logo...")
|
||||||
|
logo_path = fetcher.download_logo(info['logo_url'])
|
||||||
|
if logo_path:
|
||||||
|
print(f"Logo下载成功: {logo_path}")
|
||||||
|
else:
|
||||||
|
print("Logo下载失败")
|
||||||
|
else:
|
||||||
|
print("\n抓取失败!")
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
test_fetch()
|
||||||
1
utils/__init__.py
Normal file
1
utils/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
# Utils package
|
||||||
168
utils/website_fetcher.py
Normal file
168
utils/website_fetcher.py
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
网站信息抓取工具
|
||||||
|
"""
|
||||||
|
import requests
|
||||||
|
from bs4 import BeautifulSoup
|
||||||
|
from urllib.parse import urljoin, urlparse
|
||||||
|
import os
|
||||||
|
from PIL import Image
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
|
class WebsiteFetcher:
|
||||||
|
"""网站信息抓取器"""
|
||||||
|
|
||||||
|
def __init__(self, timeout=10):
|
||||||
|
self.timeout = timeout
|
||||||
|
self.headers = {
|
||||||
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
|
||||||
|
}
|
||||||
|
|
||||||
|
def fetch_website_info(self, url):
|
||||||
|
"""
|
||||||
|
抓取网站信息
|
||||||
|
|
||||||
|
Args:
|
||||||
|
url: 网站URL
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: 包含name, description, logo_url的字典,失败返回None
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# 确保URL包含协议
|
||||||
|
if not url.startswith(('http://', 'https://')):
|
||||||
|
url = 'https://' + url
|
||||||
|
|
||||||
|
# 请求网页
|
||||||
|
response = requests.get(url, headers=self.headers, timeout=self.timeout, allow_redirects=True)
|
||||||
|
response.raise_for_status()
|
||||||
|
response.encoding = response.apparent_encoding # 自动检测编码
|
||||||
|
|
||||||
|
# 解析HTML
|
||||||
|
soup = BeautifulSoup(response.text, 'html.parser')
|
||||||
|
|
||||||
|
# 提取信息
|
||||||
|
info = {
|
||||||
|
'name': self._extract_title(soup),
|
||||||
|
'description': self._extract_description(soup),
|
||||||
|
'logo_url': self._extract_logo(soup, url)
|
||||||
|
}
|
||||||
|
|
||||||
|
return info
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"抓取网站信息失败: {str(e)}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _extract_title(self, soup):
|
||||||
|
"""提取网站标题"""
|
||||||
|
# 优先使用 og:title
|
||||||
|
og_title = soup.find('meta', property='og:title')
|
||||||
|
if og_title and og_title.get('content'):
|
||||||
|
return og_title['content'].strip()
|
||||||
|
|
||||||
|
# 使用 title 标签
|
||||||
|
title_tag = soup.find('title')
|
||||||
|
if title_tag:
|
||||||
|
return title_tag.get_text().strip()
|
||||||
|
|
||||||
|
return ''
|
||||||
|
|
||||||
|
def _extract_description(self, soup):
|
||||||
|
"""提取网站描述"""
|
||||||
|
# 优先使用 og:description
|
||||||
|
og_desc = soup.find('meta', property='og:description')
|
||||||
|
if og_desc and og_desc.get('content'):
|
||||||
|
return og_desc['content'].strip()
|
||||||
|
|
||||||
|
# 使用 meta description
|
||||||
|
meta_desc = soup.find('meta', attrs={'name': 'description'})
|
||||||
|
if meta_desc and meta_desc.get('content'):
|
||||||
|
return meta_desc['content'].strip()
|
||||||
|
|
||||||
|
# 使用 meta keywords 作为fallback
|
||||||
|
meta_keywords = soup.find('meta', attrs={'name': 'keywords'})
|
||||||
|
if meta_keywords and meta_keywords.get('content'):
|
||||||
|
return meta_keywords['content'].strip()
|
||||||
|
|
||||||
|
return ''
|
||||||
|
|
||||||
|
def _extract_logo(self, soup, base_url):
|
||||||
|
"""提取网站Logo"""
|
||||||
|
logo_url = None
|
||||||
|
|
||||||
|
# 1. 尝试 og:image
|
||||||
|
og_image = soup.find('meta', property='og:image')
|
||||||
|
if og_image and og_image.get('content'):
|
||||||
|
logo_url = og_image['content']
|
||||||
|
|
||||||
|
# 2. 尝试 link rel="icon" 或 "shortcut icon"
|
||||||
|
if not logo_url:
|
||||||
|
icon_link = soup.find('link', rel=lambda x: x and ('icon' in x.lower() if isinstance(x, str) else 'icon' in ' '.join(x).lower()))
|
||||||
|
if icon_link and icon_link.get('href'):
|
||||||
|
logo_url = icon_link['href']
|
||||||
|
|
||||||
|
# 3. 尝试 apple-touch-icon
|
||||||
|
if not logo_url:
|
||||||
|
apple_icon = soup.find('link', rel='apple-touch-icon')
|
||||||
|
if apple_icon and apple_icon.get('href'):
|
||||||
|
logo_url = apple_icon['href']
|
||||||
|
|
||||||
|
# 4. 默认使用 /favicon.ico
|
||||||
|
if not logo_url:
|
||||||
|
logo_url = '/favicon.ico'
|
||||||
|
|
||||||
|
# 转换为绝对URL
|
||||||
|
if logo_url:
|
||||||
|
logo_url = urljoin(base_url, logo_url)
|
||||||
|
|
||||||
|
return logo_url
|
||||||
|
|
||||||
|
def download_logo(self, logo_url, save_dir='static/uploads'):
|
||||||
|
"""
|
||||||
|
下载并保存Logo
|
||||||
|
|
||||||
|
Args:
|
||||||
|
logo_url: Logo的URL
|
||||||
|
save_dir: 保存目录
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: 保存后的相对路径,失败返回None
|
||||||
|
"""
|
||||||
|
if not logo_url:
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 创建保存目录
|
||||||
|
os.makedirs(save_dir, exist_ok=True)
|
||||||
|
|
||||||
|
# 下载图片
|
||||||
|
response = requests.get(logo_url, headers=self.headers, timeout=self.timeout)
|
||||||
|
response.raise_for_status()
|
||||||
|
|
||||||
|
# 检查是否是图片
|
||||||
|
content_type = response.headers.get('content-type', '')
|
||||||
|
if not content_type.startswith('image/'):
|
||||||
|
return None
|
||||||
|
|
||||||
|
# 生成文件名
|
||||||
|
parsed_url = urlparse(logo_url)
|
||||||
|
ext = os.path.splitext(parsed_url.path)[1]
|
||||||
|
if not ext or len(ext) > 5:
|
||||||
|
ext = '.png' # 默认扩展名
|
||||||
|
|
||||||
|
# 使用域名作为文件名
|
||||||
|
domain = parsed_url.netloc.replace(':', '_').replace('.', '_')
|
||||||
|
filename = f"logo_{domain}{ext}"
|
||||||
|
filepath = os.path.join(save_dir, filename)
|
||||||
|
|
||||||
|
# 保存图片
|
||||||
|
with open(filepath, 'wb') as f:
|
||||||
|
f.write(response.content)
|
||||||
|
|
||||||
|
# 返回相对路径(用于数据库存储)
|
||||||
|
return f'/{filepath.replace(os.sep, "/")}'
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"下载Logo失败: {str(e)}")
|
||||||
|
return None
|
||||||
Reference in New Issue
Block a user