- 前台页面全面升级为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>
244 lines
8.2 KiB
Python
244 lines
8.2 KiB
Python
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)
|