From 236b202e3e3c5408a9707d31f53d680f430fdd2b Mon Sep 17 00:00:00 2001 From: coder Date: Tue, 7 Apr 2026 23:08:49 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=90=8E=E5=8F=B0=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 功能: - 数据概览仪表盘 - 用户管理(列表/编辑/删除) - 翻译记录管理 - 缓存管理 - 统计报表(图表) - 操作日志 管理员账号: admin / admin123 后台地址: /admin/ --- __pycache__/admin.cpython-310.pyc | Bin 0 -> 9950 bytes __pycache__/config.cpython-310.pyc | Bin 0 -> 1654 bytes __pycache__/models.cpython-310.pyc | Bin 0 -> 7340 bytes __pycache__/services.cpython-310.pyc | Bin 0 -> 9001 bytes admin.py | 428 ++++++++++++++++++++++++ app.py | 21 +- instance/pdf_translate.db | Bin 0 -> 45056 bytes models.py | 76 ++++- templates/admin/cache.html | 140 ++++++++ templates/admin/dashboard.html | 294 ++++++++++++++++ templates/admin/logs.html | 103 ++++++ templates/admin/settings.html | 114 +++++++ templates/admin/stats.html | 160 +++++++++ templates/admin/translation_detail.html | 113 +++++++ templates/admin/translations.html | 122 +++++++ templates/admin/user_detail.html | 180 ++++++++++ templates/admin/users.html | 148 ++++++++ 17 files changed, 1896 insertions(+), 3 deletions(-) create mode 100644 __pycache__/admin.cpython-310.pyc create mode 100644 __pycache__/config.cpython-310.pyc create mode 100644 __pycache__/models.cpython-310.pyc create mode 100644 __pycache__/services.cpython-310.pyc create mode 100644 admin.py create mode 100644 instance/pdf_translate.db create mode 100644 templates/admin/cache.html create mode 100644 templates/admin/dashboard.html create mode 100644 templates/admin/logs.html create mode 100644 templates/admin/settings.html create mode 100644 templates/admin/stats.html create mode 100644 templates/admin/translation_detail.html create mode 100644 templates/admin/translations.html create mode 100644 templates/admin/user_detail.html create mode 100644 templates/admin/users.html diff --git a/__pycache__/admin.cpython-310.pyc b/__pycache__/admin.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d7fb5528f3c08c95c09d0fdeb163b42c694ad6e4 GIT binary patch literal 9950 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 V|0xIgT{c7hKfFU2huMOze*nlqA{+n! literal 0 HcmV?d00001 diff --git a/__pycache__/models.cpython-310.pyc b/__pycache__/models.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0f9ec01a9df89f7571985722dd94a623f81836cf GIT binary patch literal 7340 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..88b5b2589a5ec877094ed293418f6acf8daf10c1 GIT binary patch literal 45056 zcmeI*(Qn#D90zc_X@SsXOw%Mk>|s{bszF^++t`4!O)AaYE^R_mO4jwsJM4p41?<|U zYx=OMX*X&A#k7B5f64xeJ@>hL+wM#t7)sh{ij-wvCnRuZY~SbiyE{y%d;i|17gM?I zhdmO@CE)`>6onhIEC@o5-7d1*XvwfU=SL6NZ*k)Oyt_GJ^}(H`!k_IIlAE=go0goelb7?0o-1$Eo7QcsA=kH?a(#Dm^HVv$IEZLyAJvVD`lLtYuWOC< zuWF6us#4_T#NoRhml{ejwWXMZ9U70TRb}`rm&U~F%1!H=Cf|q`*u-^18b#wZbtBol z6H*dW*JktMm#iVpjV+5mVW+XNRckzy?^+M#WzS7ky|vx2Hg4B>+45lnigLrcWi_n& zx^-XXWl^$rzWAfGn3b+v5r2N15XK?tN8JQ5n!KM+@lDqJGrYFfrS=|)_QuUC>uD!H z^g3RjbZx$B%2_jv^s33mBJUY}W9S0Xp|;}>`tcj9BYzM&)b5ggXTa7RACEeW%P?H? zs}G&19gvXpqHzGDn8bsl;Az9H1)<+bf_j)^JaRM){m|~QQ1ji762KqC!60T^jgN?U z!}&Ajq=4i1f-ZeSZnQ(cXFJ5%qw>w|?MEi*7;_=>DV`x8~O89@5 zVtkjA2|3l@KIC-f%~o6Z`YESlYJNSMKhO?wyuhT*LUgD z=y8|52DZr{_LIkUswK9XdboJ-aV9INs`!E=IvpNHds1<>B%5-Ytv?1UVp+<^RW=fi zM!o5NJAdSI*#Nhn?uk4pVn1fRJk>#YahFUqgxcIT8Sr@BoDMLZp!wp#*7>Y-{kr&a zV|r8@_J;|mVZnuz%}n(%G;bf#k{&qQqf@iPTL!C{k&bKZdGRdY93uKO@Yn$KF0;od zNyiOiiCVjmk#?`oOVk-ITb(J&y!c$qN|Gf0^dJdIG6y9;&ZX!N=cZ#P z^Tl&-UsX+3U0^RZ`T4!D#BSpIe+ldx3j`nl0SG_<0uX=z1Rwwb2teSh3OxT_EC|xI zgL{&Y%L%pG;$m{*p8U8G2YbX@WpDDbw;=a}c&(&W%GI^%ntJ(8yR+WecDj%5^z^&_ zj(gdqra{|fU2p4#RyIwC686ppRavRtGH9z@YEe^FjjB>H6sJU6DpgCZ3Uv+DHK{^d z72>LjOUqT4Qb#RQ-PK6ja9Y)pThc40QY}^Ww%Jk@s(CEy6c*zQfj*zO%7O7 zwDO9guP7B+HP)2MnxU>CRNAOHafKmY;|fB*y_009U<00O5&U|Y-yyPxn&6)Nl^g4MF3 zt+Y(VUC|w + + + + 缓存管理 - 后台管理 + + + + + + + +
+ +
+
+
+
+
缓存总数
+

{{ 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