diff --git a/__pycache__/admin.cpython-310.pyc b/__pycache__/admin.cpython-310.pyc new file mode 100644 index 0000000..d7fb552 Binary files /dev/null and b/__pycache__/admin.cpython-310.pyc differ diff --git a/__pycache__/config.cpython-310.pyc b/__pycache__/config.cpython-310.pyc new file mode 100644 index 0000000..338e8d4 Binary files /dev/null and b/__pycache__/config.cpython-310.pyc differ diff --git a/__pycache__/models.cpython-310.pyc b/__pycache__/models.cpython-310.pyc new file mode 100644 index 0000000..0f9ec01 Binary files /dev/null and b/__pycache__/models.cpython-310.pyc differ diff --git a/__pycache__/services.cpython-310.pyc b/__pycache__/services.cpython-310.pyc new file mode 100644 index 0000000..f8884a9 Binary files /dev/null and b/__pycache__/services.cpython-310.pyc differ diff --git a/admin.py b/admin.py new file mode 100644 index 0000000..9a150d9 --- /dev/null +++ b/admin.py @@ -0,0 +1,428 @@ +""" +后台管理模块 +""" + +from datetime import datetime, date, timedelta +from functools import wraps +from flask import Blueprint, render_template, request, jsonify, redirect, url_for, session, flash +from sqlalchemy import func, desc + +from models import db, User, Translation, TranslationCache, GuestTranslation, SystemConfig, OperationLog +from config import USER_LIMITS, MEMBERSHIP_PLANS + +admin_bp = Blueprint('admin', __name__, url_prefix='/admin') + + +# ==================== 权限装饰器 ==================== +def admin_required(f): + """管理员权限装饰器""" + @wraps(f) + def decorated_function(*args, **kwargs): + user_id = session.get('user_id') + if not user_id: + return redirect(url_for('login', next=request.url)) + + user = User.query.get(user_id) + if not user or not user.is_admin: + flash('需要管理员权限', 'error') + return redirect(url_for('index')) + + return f(*args, **kwargs) + return decorated_function + + +# ==================== 后台首页 ==================== +@admin_bp.route('/') +@admin_required +def dashboard(): + """后台首页 - 数据概览""" + # 用户统计 + total_users = User.query.count() + new_users_today = User.query.filter( + func.date(User.created_at) == date.today() + ).count() + vip_users = User.query.filter(User.user_type.startswith('vip')).count() + + # 翻译统计 + total_translations = Translation.query.count() + today_translations = Translation.query.filter( + func.date(Translation.created_at) == date.today() + ).count() + + # 缓存统计 + total_cache = TranslationCache.query.count() + total_cache_hits = db.session.query(func.sum(TranslationCache.hit_count)).scalar() or 0 + total_cache_size = db.session.query(func.sum(TranslationCache.file_size)).scalar() or 0 + + # 最近翻译 + recent_translations = Translation.query.order_by(desc(Translation.created_at)).limit(10).all() + + # 最近用户 + recent_users = User.query.order_by(desc(User.created_at)).limit(10).all() + + # 每日翻译趋势(最近7天) + daily_stats = [] + for i in range(6, -1, -1): + d = date.today() - timedelta(days=i) + count = Translation.query.filter(func.date(Translation.created_at) == d).count() + daily_stats.append({'date': d.strftime('%m-%d'), 'count': count}) + + return render_template('admin/dashboard.html', + total_users=total_users, + new_users_today=new_users_today, + vip_users=vip_users, + total_translations=total_translations, + today_translations=today_translations, + total_cache=total_cache, + total_cache_hits=total_cache_hits, + total_cache_size=total_cache_size, + recent_translations=recent_translations, + recent_users=recent_users, + daily_stats=daily_stats, + ) + + +# ==================== 用户管理 ==================== +@admin_bp.route('/users') +@admin_required +def users(): + """用户列表""" + page = request.args.get('page', 1, type=int) + search = request.args.get('search', '') + user_type = request.args.get('type', '') + + query = User.query + + if search: + query = query.filter( + (User.username.contains(search)) | (User.email.contains(search)) + ) + + if user_type: + query = query.filter_by(user_type=user_type) + + users = query.order_by(desc(User.created_at)).paginate(page=page, per_page=20) + + return render_template('admin/users.html', + users=users, + search=search, + user_type=user_type, + user_types=['free', 'vip_basic', 'vip_pro', 'vip_enterprise', 'admin'] + ) + + +@admin_bp.route('/user/', methods=['GET', 'POST']) +@admin_required +def user_detail(user_id): + """用户详情/编辑""" + user = User.query.get_or_404(user_id) + + if request.method == 'POST': + # 更新用户信息 + data = request.json if request.is_json else request.form + + user.username = data.get('username', user.username) + user.email = data.get('email', user.email) + user.user_type = data.get('user_type', user.user_type) + user.is_active = data.get('is_active', user.is_active) == 'true' or data.get('is_active') == True + user.is_admin = data.get('is_admin', user.is_admin) == 'true' or data.get('is_admin') == True + + # 会员到期时间 + expire_str = data.get('membership_expire') + if expire_str: + try: + user.membership_expire = datetime.fromisoformat(expire_str) + except: + pass + + # 密码 + new_password = data.get('new_password') + if new_password: + user.set_password(new_password) + + db.session.commit() + + # 记录日志 + log = OperationLog( + user_id=session.get('user_id'), + username='admin', + action='edit_user', + target=user.username, + detail=str(data) + ) + db.session.add(log) + db.session.commit() + + if request.is_json: + return jsonify({'success': True, 'user': user.to_dict()}) + flash('用户信息已更新', 'success') + return redirect(url_for('admin.user_detail', user_id=user_id)) + + # 获取用户翻译记录 + translations = Translation.query.filter_by(user_id=user_id).order_by(desc(Translation.created_at)).limit(20).all() + + return render_template('admin/user_detail.html', user=user, translations=translations) + + +@admin_bp.route('/user//delete', methods=['POST']) +@admin_required +def delete_user(user_id): + """删除用户""" + user = User.query.get_or_404(user_id) + + if user.is_admin and User.query.filter_by(is_admin=True).count() <= 1: + return jsonify({'error': '不能删除最后一个管理员'}), 400 + + username = user.username + db.session.delete(user) + db.session.commit() + + # 记录日志 + log = OperationLog( + user_id=session.get('user_id'), + username='admin', + action='delete_user', + target=username + ) + db.session.add(log) + db.session.commit() + + return jsonify({'success': True}) + + +# ==================== 翻译记录管理 ==================== +@admin_bp.route('/translations') +@admin_required +def translations(): + """翻译记录列表""" + page = request.args.get('page', 1, type=int) + status = request.args.get('status', '') + search = request.args.get('search', '') + + query = Translation.query + + if status: + query = query.filter_by(status=status) + + if search: + query = query.filter(Translation.original_filename.contains(search)) + + translations = query.order_by(desc(Translation.created_at)).paginate(page=page, per_page=20) + + return render_template('admin/translations.html', + translations=translations, + status=status, + search=search + ) + + +@admin_bp.route('/translation/') +@admin_required +def translation_detail(trans_id): + """翻译详情""" + translation = Translation.query.get_or_404(trans_id) + + # 获取关联用户 + user = User.query.get(translation.user_id) if translation.user_id else None + + return render_template('admin/translation_detail.html', + translation=translation, + user=user + ) + + +@admin_bp.route('/translation//delete', methods=['POST']) +@admin_required +def delete_translation(trans_id): + """删除翻译记录""" + translation = Translation.query.get_or_404(trans_id) + + # 删除文件 + if translation.output_path: + import os + if os.path.exists(translation.output_path): + os.remove(translation.output_path) + + db.session.delete(translation) + db.session.commit() + + return jsonify({'success': True}) + + +# ==================== 缓存管理 ==================== +@admin_bp.route('/cache') +@admin_required +def cache_list(): + """缓存列表""" + page = request.args.get('page', 1, type=int) + + caches = TranslationCache.query.order_by(desc(TranslationCache.hit_count)).paginate(page=page, per_page=20) + + # 统计 + total_size = db.session.query(func.sum(TranslationCache.file_size)).scalar() or 0 + total_hits = db.session.query(func.sum(TranslationCache.hit_count)).scalar() or 0 + + return render_template('admin/cache.html', + caches=caches, + total_size=total_size, + total_hits=total_hits + ) + + +@admin_bp.route('/cache//delete', methods=['POST']) +@admin_required +def delete_cache(cache_id): + """删除缓存""" + cache = TranslationCache.query.get_or_404(cache_id) + + # 删除文件 + if cache.cache_path: + import os + if os.path.exists(cache.cache_path): + os.remove(cache.cache_path) + + db.session.delete(cache) + db.session.commit() + + return jsonify({'success': True}) + + +@admin_bp.route('/cache/clear', methods=['POST']) +@admin_required +def clear_cache(): + """清空缓存""" + import os + caches = TranslationCache.query.all() + + for cache in caches: + if cache.cache_path and os.path.exists(cache.cache_path): + os.remove(cache.cache_path) + + TranslationCache.query.delete() + db.session.commit() + + return jsonify({'success': True, 'deleted': len(caches)}) + + +# ==================== 系统配置 ==================== +@admin_bp.route('/settings', methods=['GET', 'POST']) +@admin_required +def settings(): + """系统配置""" + if request.method == 'POST': + data = request.json if request.is_json else request.form + + for key, value in data.items(): + SystemConfig.set(key, value) + + if request.is_json: + return jsonify({'success': True}) + flash('配置已保存', 'success') + + # 获取所有配置 + configs = SystemConfig.query.all() + config_dict = {c.key: c.value for c in configs} + + return render_template('admin/settings.html', + configs=config_dict, + user_limits=USER_LIMITS, + membership_plans=MEMBERSHIP_PLANS + ) + + +# ==================== 操作日志 ==================== +@admin_bp.route('/logs') +@admin_required +def logs(): + """操作日志""" + page = request.args.get('page', 1, type=int) + action = request.args.get('action', '') + + query = OperationLog.query + + if action: + query = query.filter_by(action=action) + + logs = query.order_by(desc(OperationLog.created_at)).paginate(page=page, per_page=50) + + return render_template('admin/logs.html', logs=logs, action=action) + + +# ==================== 统计报表 ==================== +@admin_bp.route('/stats') +@admin_required +def stats(): + """统计报表""" + # 用户增长趋势(最近30天) + user_growth = [] + for i in range(29, -1, -1): + d = date.today() - timedelta(days=i) + count = User.query.filter(func.date(User.created_at) == d).count() + user_growth.append({'date': d.strftime('%Y-%m-%d'), 'count': count}) + + # 翻译量趋势(最近30天) + translation_growth = [] + for i in range(29, -1, -1): + d = date.today() - timedelta(days=i) + count = Translation.query.filter(func.date(Translation.created_at) == d).count() + translation_growth.append({'date': d.strftime('%Y-%m-%d'), 'count': count}) + + # 用户类型分布 + user_distribution = db.session.query( + User.user_type, func.count(User.id) + ).group_by(User.user_type).all() + + # 翻译状态分布 + status_distribution = db.session.query( + Translation.status, func.count(Translation.id) + ).group_by(Translation.status).all() + + # Top用户 + top_users = db.session.query( + User.username, func.count(Translation.id).label('count') + ).join(Translation).group_by(User.id).order_by(desc('count')).limit(10).all() + + return render_template('admin/stats.html', + user_growth=user_growth, + translation_growth=translation_growth, + user_distribution=user_distribution, + status_distribution=status_distribution, + top_users=top_users + ) + + +# ==================== API接口 ==================== +@admin_bp.route('/api/stats/summary') +@admin_required +def api_stats_summary(): + """API: 统计摘要""" + return jsonify({ + 'total_users': User.query.count(), + 'today_users': User.query.filter(func.date(User.created_at) == date.today()).count(), + 'total_translations': Translation.query.count(), + 'today_translations': Translation.query.filter(func.date(Translation.created_at) == date.today()).count(), + 'total_cache': TranslationCache.query.count(), + 'cache_hits': db.session.query(func.sum(TranslationCache.hit_count)).scalar() or 0, + }) + + +@admin_bp.route('/api/user//upgrade', methods=['POST']) +@admin_required +def api_user_upgrade(user_id): + """API: 升级用户会员""" + user = User.query.get_or_404(user_id) + data = request.json + + user_type = data.get('user_type') + months = data.get('months', 1) + + if user_type not in USER_LIMITS: + return jsonify({'error': '无效的用户类型'}), 400 + + user.user_type = user_type + if user_type.startswith('vip'): + user.membership_expire = datetime.utcnow() + timedelta(days=30 * months) + + db.session.commit() + + return jsonify({'success': True, 'user': user.to_dict()}) \ No newline at end of file diff --git a/app.py b/app.py index 21bc371..15066dd 100644 --- a/app.py +++ b/app.py @@ -15,6 +15,7 @@ from werkzeug.utils import secure_filename from config import * from models import db, User, Translation, TranslationCache, GuestTranslation from services import TranslationService, CacheService, TranslationTask +from admin import admin_bp # ==================== 创建应用 ==================== app = Flask(__name__) @@ -26,6 +27,9 @@ app.config['MAX_CONTENT_LENGTH'] = MAX_FILE_SIZE # 初始化数据库 db.init_app(app) +# 注册后台管理蓝图 +app.register_blueprint(admin_bp) + # 初始化服务 cache_service = CacheService(CACHE_DIR, CACHE_EXPIRE_DAYS) @@ -495,8 +499,23 @@ def init_app(): # 创建数据库表 with app.app_context(): db.create_all() + + # 创建默认管理员账号 + admin = User.query.filter_by(username='admin').first() + if not admin: + admin = User( + username='admin', + email='admin@tphai.com', + user_type='admin', + is_admin=True, + is_active=True + ) + admin.set_password('admin123') + db.session.add(admin) + db.session.commit() + print("✅ 默认管理员账号已创建: admin / admin123") if __name__ == '__main__': init_app() - app.run(host='0.0.0.0', port=5000, debug=True) \ No newline at end of file + app.run(host='0.0.0.0', port=19000, debug=True) \ No newline at end of file diff --git a/instance/pdf_translate.db b/instance/pdf_translate.db new file mode 100644 index 0000000..88b5b25 Binary files /dev/null and b/instance/pdf_translate.db differ diff --git a/models.py b/models.py index 29dfd3b..670055b 100644 --- a/models.py +++ b/models.py @@ -19,7 +19,7 @@ class User(db.Model): email = db.Column(db.String(120), unique=True, nullable=False) password_hash = db.Column(db.String(256), nullable=False) - # 用户类型: guest, free, vip_basic, vip_pro, vip_enterprise + # 用户类型: guest, free, vip_basic, vip_pro, vip_enterprise, admin user_type = db.Column(db.String(20), default='free') # 会员信息 @@ -34,6 +34,10 @@ class User(db.Model): created_at = db.Column(db.DateTime, default=datetime.utcnow) updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) + # 状态 + is_active = db.Column(db.Boolean, default=True) # 是否启用 + is_admin = db.Column(db.Boolean, default=False) # 是否管理员 + # 关系 translations = db.relationship('Translation', backref='user', lazy=True) @@ -92,8 +96,12 @@ class User(db.Model): 'email': self.email, 'user_type': self.user_type, 'is_vip': self.is_vip(), + 'is_admin': self.is_admin, + 'is_active': self.is_active, 'daily_count': self.daily_count, 'total_count': self.total_count, + 'created_at': self.created_at.isoformat() if self.created_at else None, + 'membership_expire': self.membership_expire.isoformat() if self.membership_expire else None, } @@ -145,8 +153,10 @@ class Translation(db.Model): 'status': self.status, 'progress': self.progress, 'from_cache': self.from_cache, + 'file_size': self.file_size, 'created_at': self.created_at.isoformat() if self.created_at else None, 'completed_at': self.completed_at.isoformat() if self.completed_at else None, + 'user_id': self.user_id, } @@ -163,6 +173,7 @@ class TranslationCache(db.Model): # 缓存信息 cache_path = db.Column(db.String(255), nullable=False) # 缓存文件路径 page_count = db.Column(db.Integer, nullable=False) + file_size = db.Column(db.Integer, default=0) # 统计 hit_count = db.Column(db.Integer, default=0) # 缓存命中次数 @@ -194,6 +205,67 @@ class GuestTranslation(db.Model): # 统计 daily_count = db.Column(db.Integer, default=0) + total_count = db.Column(db.Integer, default=0) last_translate_date = db.Column(db.Date, nullable=True) - created_at = db.Column(db.DateTime, default=datetime.utcnow) \ No newline at end of file + created_at = db.Column(db.DateTime, default=datetime.utcnow) + + +# ==================== 系统配置模型 ==================== +class SystemConfig(db.Model): + """系统配置表""" + __tablename__ = 'system_config' + + id = db.Column(db.Integer, primary_key=True) + key = db.Column(db.String(100), unique=True, nullable=False) + value = db.Column(db.Text, nullable=True) + description = db.Column(db.String(255), nullable=True) + updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) + + @staticmethod + def get(key, default=None): + config = SystemConfig.query.filter_by(key=key).first() + return config.value if config else default + + @staticmethod + def set(key, value, description=None): + config = SystemConfig.query.filter_by(key=key).first() + if config: + config.value = value + else: + config = SystemConfig(key=key, value=value, description=description) + db.session.add(config) + db.session.commit() + + +# ==================== 操作日志模型 ==================== +class OperationLog(db.Model): + """操作日志表""" + __tablename__ = 'operation_logs' + + id = db.Column(db.Integer, primary_key=True) + + # 操作者 + user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=True) + username = db.Column(db.String(80), nullable=True) + + # 操作信息 + action = db.Column(db.String(50), nullable=False) # login, translate, register, etc. + target = db.Column(db.String(100), nullable=True) # 操作对象 + detail = db.Column(db.Text, nullable=True) # 详细信息(JSON) + + # IP地址 + ip_address = db.Column(db.String(45), nullable=True) + + # 时间 + created_at = db.Column(db.DateTime, default=datetime.utcnow) + + def to_dict(self): + return { + 'id': self.id, + 'username': self.username, + 'action': self.action, + 'target': self.target, + 'ip_address': self.ip_address, + 'created_at': self.created_at.isoformat() if self.created_at else None, + } \ No newline at end of file diff --git a/templates/admin/cache.html b/templates/admin/cache.html new file mode 100644 index 0000000..f29363a --- /dev/null +++ b/templates/admin/cache.html @@ -0,0 +1,140 @@ + + + + + 缓存管理 - 后台管理 + + + + + + + +
+ +
+
+
+
+
缓存总数
+

{{ caches.total }}

+
+
+
+
+
+
+
缓存大小
+

{{ (total_size / 1024 / 1024)|round(2) }} MB

+
+
+
+
+
+
+
总命中次数
+

{{ total_hits }}

+
+
+
+
+ +
+
+
缓存列表
+ +
+
+ + + + + + + + + + + + + {% for c in caches.items %} + + + + + + + + + {% else %} + + {% endfor %} + +
文件哈希页数大小命中次数创建时间操作
{{ c.file_hash[:16] }}...{{ c.page_count }}{{ (c.file_size / 1024)|round(1) }}KB{{ c.hit_count }}{{ c.created_at.strftime('%m-%d %H:%M') }} + +
暂无缓存
+
+ +
+
+ + + + \ No newline at end of file diff --git a/templates/admin/dashboard.html b/templates/admin/dashboard.html new file mode 100644 index 0000000..3733643 --- /dev/null +++ b/templates/admin/dashboard.html @@ -0,0 +1,294 @@ + + + + + + 后台管理 - PDF翻译助手 + + + + + + + + + +
+ +
+
+
+
+
+
+
总用户数
+
{{ total_users }}
+
今日 +{{ new_users_today }}
+
+
+
+
+
+
+
+
+
+
总翻译次数
+
{{ total_translations }}
+
今日 +{{ today_translations }}
+
+
+
+
+
+
+
+
+
+
VIP用户
+
{{ vip_users }}
+
+
+
+
+
+
+
+
+
+
缓存数量
+
{{ total_cache }}
+
命中 {{ total_cache_hits }} 次
+
+
+
+
+
+ +
+ +
+
+
+
每日翻译趋势(最近7天)
+
+
+ +
+
+
+ + + +
+ +
+ +
+
+
+
最近翻译
+ 查看全部 +
+
+ + + + + + + + + + {% for t in recent_translations %} + + + + + + {% endfor %} + +
文件名状态时间
{{ t.original_filename[:30] }}{% if t.original_filename|length > 30 %}...{% endif %} + + {{ t.status }} + + {{ t.created_at.strftime('%H:%M') }}
+
+
+
+ + +
+
+
+
最近注册
+ 查看全部 +
+
+ + + + + + + + + + {% for u in recent_users %} + + + + + + {% endfor %} + +
用户名类型注册时间
{{ u.username }} + + {{ u.user_type }} + + {{ u.created_at.strftime('%m-%d %H:%M') }}
+
+
+
+
+
+ + + + + \ No newline at end of file diff --git a/templates/admin/logs.html b/templates/admin/logs.html new file mode 100644 index 0000000..d7cbaef --- /dev/null +++ b/templates/admin/logs.html @@ -0,0 +1,103 @@ + + + + + 操作日志 - 后台管理 + + + + + + + +
+
+
+
+
操作日志
+
+
+ + +
+
+
+
+
+ + + + + + + + + + + + + {% for log in logs.items %} + + + + + + + + + {% else %} + + {% endfor %} + +
ID操作者操作类型操作对象IP地址时间
{{ log.id }}{{ log.username or '系统' }} + + {{ log.action }} + + {{ log.target or '-' }}{{ log.ip_address or '-' }}{{ log.created_at.strftime('%Y-%m-%d %H:%M:%S') }}
暂无日志
+
+ +
+
+ + \ No newline at end of file diff --git a/templates/admin/settings.html b/templates/admin/settings.html new file mode 100644 index 0000000..a0ab377 --- /dev/null +++ b/templates/admin/settings.html @@ -0,0 +1,114 @@ + + + + + 系统配置 - 后台管理 + + + + + + + +
+
+
+
+
用户权限配置
+
+ + + + + + + + + + {% for type, limits in user_limits.items() %} + + + + + + {% endfor %} + +
用户类型每日次数最大页数
{{ type }}{{ limits.daily_translations if limits.daily_translations > 0 else '无限' }}{{ limits.max_pages if limits.max_pages > 0 else '无限' }}
+ 权限配置需修改 config.py 文件 +
+
+
+ +
+
+
会员套餐配置
+
+ + + + + + + + + + {% for key, plan in membership_plans.items() %} + + + + + + {% endfor %} + +
套餐价格周期
{{ plan.name }}¥{{ plan.price }}{{ plan.period }}
+ 套餐配置需修改 config.py 文件 +
+
+
+
+ +
+
系统信息
+
+
+
+

应用名称: PDF翻译助手

+

版本: 1.0.0

+

框架: Flask + SQLAlchemy

+
+
+

LLM模型: qwen/qwen3.5-35b-a3b

+

API地址: http://192.168.2.5:1234/v1

+
+
+

缓存有效期: 30天

+

最大文件: 50MB

+
+
+
+
+
+ + \ No newline at end of file diff --git a/templates/admin/stats.html b/templates/admin/stats.html new file mode 100644 index 0000000..7df0e63 --- /dev/null +++ b/templates/admin/stats.html @@ -0,0 +1,160 @@ + + + + + 统计报表 - 后台管理 + + + + + + + + +
+
+ +
+
+
用户增长趋势(30天)
+
+ +
+
+
+ +
+
+
翻译量趋势(30天)
+
+ +
+
+
+
+ +
+ +
+
+
用户类型分布
+
+ +
+
+
+ +
+
+
翻译状态分布
+
+ +
+
+
+ +
+
+
活跃用户Top10
+
+ + + + + + {% for name, count in top_users %} + + + + + + {% else %} + + {% endfor %} + +
排名用户翻译数
{{ loop.index }}{{ name }}{{ count }}
暂无数据
+
+
+
+
+
+ + + + \ No newline at end of file diff --git a/templates/admin/translation_detail.html b/templates/admin/translation_detail.html new file mode 100644 index 0000000..993d51c --- /dev/null +++ b/templates/admin/translation_detail.html @@ -0,0 +1,113 @@ + + + + + 翻译详情 - 后台管理 + + + + + + + +
+
+
+
翻译详情
+ +
+
+
+
+

文件名: {{ translation.original_filename }}

+

页数: {{ translation.page_count }}

+

大小: {{ (translation.file_size / 1024)|round(1) }} KB

+
+
+

状态: + + {{ translation.status }} + + {% if translation.from_cache %} + 来自缓存 + {% endif %} +

+

用户: {% if user %}{{ user.username }}{% else %}访客{% endif %}

+

时间: {{ translation.created_at.strftime('%Y-%m-%d %H:%M:%S') }}

+
+
+ + {% if translation.status == 'completed' %} +
+
翻译结果
+
+ 加载中... +
+ {% elif translation.status == 'failed' %} +
+ 错误信息: {{ translation.error_message or '未知错误' }} +
+ {% endif %} + + {% if translation.translate_params %} +
+
翻译参数
+
{{ translation.translate_params }}
+ {% endif %} +
+
+
+ + + + \ No newline at end of file diff --git a/templates/admin/translations.html b/templates/admin/translations.html new file mode 100644 index 0000000..fa8213f --- /dev/null +++ b/templates/admin/translations.html @@ -0,0 +1,122 @@ + + + + + 翻译记录 - 后台管理 + + + + + + + +
+
+
+
+
翻译记录
+
+
+ + + +
+
+
+
+
+ + + + + + + + + + + + + + + + {% for t in translations.items %} + + + + + + + + + + + + {% else %} + + {% endfor %} + +
ID文件名用户页数大小状态缓存时间操作
{{ t.id }}{{ t.original_filename[:25] }}{% if t.original_filename|length > 25 %}...{% endif %}{% if t.user_id %}ID:{{ t.user_id }}{% else %}访客{% endif %}{{ t.page_count }}{{ (t.file_size / 1024)|round(1) }}KB + + {{ t.status }} + + {% if t.from_cache %}{% else %}-{% endif %}{{ t.created_at.strftime('%m-%d %H:%M') }} + + +
暂无数据
+
+ +
+
+ + + + \ No newline at end of file diff --git a/templates/admin/user_detail.html b/templates/admin/user_detail.html new file mode 100644 index 0000000..8f0973a --- /dev/null +++ b/templates/admin/user_detail.html @@ -0,0 +1,180 @@ + + + + + + 用户详情 - 后台管理 + + + + + + + +
+
+ +
+
+
+
用户信息
+
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+
+
+ + +
+
+
统计信息
+
+
+

今日翻译: {{ user.daily_count }}

+

总翻译: {{ user.total_count }}

+

注册时间: {{ user.created_at.strftime('%Y-%m-%d %H:%M') }}

+

最后翻译: {% if user.last_translate_date %}{{ user.last_translate_date }}{% else %}无{% endif %}

+
+
+
+ + +
+
+
+
翻译记录
+
+
+ + + + + + + + + + + + + {% for t in translations %} + + + + + + + + + {% else %} + + {% endfor %} + +
文件名页数状态缓存时间操作
{{ t.original_filename[:30] }}{% if t.original_filename|length > 30 %}...{% endif %}{{ t.page_count }} + + {{ t.status }} + + {% if t.from_cache %}{% else %}-{% endif %}{{ t.created_at.strftime('%m-%d %H:%M') }} + + + +
暂无翻译记录
+
+
+
+
+
+ + + + \ No newline at end of file diff --git a/templates/admin/users.html b/templates/admin/users.html new file mode 100644 index 0000000..fc1e59f --- /dev/null +++ b/templates/admin/users.html @@ -0,0 +1,148 @@ + + + + + + 用户管理 - 后台管理 + + + + + + + + + +
+
+
+
+
+
用户管理
+
+
+
+ + + +
+
+
+
+
+ + + + + + + + + + + + + + + {% for user in users.items %} + + + + + + + + + + + {% else %} + + {% endfor %} + +
ID用户名邮箱类型翻译次数状态注册时间操作
{{ user.id }} + {{ user.username }} + {% if user.is_admin %}管理员{% endif %} + {{ user.email }} + + {{ user.user_type }} + + + {{ user.daily_count }} / + {{ user.total_count }} + + {% if user.is_active %} + 正常 + {% else %} + 禁用 + {% endif %} + {{ user.created_at.strftime('%Y-%m-%d %H:%M') }} + + + + +
暂无数据
+
+ +
+
+ + + + \ No newline at end of file