diff --git a/admin.py b/admin.py index 9f6478b..05dd594 100644 --- a/admin.py +++ b/admin.py @@ -9,7 +9,8 @@ from sqlalchemy import func, desc import json from models import (db, User, Translation, TranslationCache, GuestTranslation, - SystemConfig, OperationLog, DataPackage, UserPackage, DynamicConfig) + SystemConfig, OperationLog, DataPackage, UserPackage, DynamicConfig, + UserTypeConfig, MembershipPlanConfig) from config import USER_LIMITS, MEMBERSHIP_PLANS admin_bp = Blueprint('admin', __name__, url_prefix='/admin') @@ -771,4 +772,449 @@ def get_llm_config(): 'max_tokens': DynamicConfig.get('llm_max_tokens', LLM_CONFIG.get('max_tokens')), 'chunk_size': DynamicConfig.get('llm_chunk_size', LLM_CONFIG.get('chunk_size')), 'timeout': DynamicConfig.get('llm_timeout', LLM_CONFIG.get('timeout')), - } \ No newline at end of file + } + + +# ==================== 用户类型配置管理(动态增删) ==================== +@admin_bp.route('/user-types') +@admin_required +def user_types(): + """用户类型配置列表""" + user_types = UserTypeConfig.query.order_by(UserTypeConfig.sort_order).all() + + # 如果数据库中没有数据,初始化默认配置 + if not user_types: + init_default_user_types() + user_types = UserTypeConfig.query.order_by(UserTypeConfig.sort_order).all() + + return render_template('admin/user_types.html', user_types=user_types) + + +@admin_bp.route('/user-types/add', methods=['GET', 'POST']) +@admin_required +def add_user_type(): + """添加用户类型""" + if request.method == 'POST': + data = request.json if request.is_json else request.form + + # 检查type_key是否已存在 + existing = UserTypeConfig.query.filter_by(type_key=data.get('type_key')).first() + if existing: + return jsonify({'error': '类型标识已存在'}), 400 + + user_type = UserTypeConfig( + type_key=data.get('type_key'), + display_name=data.get('display_name'), + daily_translations=int(data.get('daily_translations', 10)), + max_pages=int(data.get('max_pages', 50)), + max_file_size=int(data.get('max_file_size_mb', 30)) * 1024 * 1024, + features=data.get('features', '[]'), + sort_order=int(data.get('sort_order', 0)), + is_active=data.get('is_active', True), + is_system=False, + ) + + db.session.add(user_type) + db.session.commit() + + # 记录日志 + log = OperationLog( + user_id=session.get('user_id'), + username='admin', + action='add_user_type', + target=user_type.type_key, + detail=json.dumps(user_type.to_dict(), ensure_ascii=False) + ) + db.session.add(log) + db.session.commit() + + if request.is_json: + return jsonify({'success': True, 'user_type': user_type.to_dict()}) + flash('用户类型已添加', 'success') + return redirect(url_for('admin.user_types')) + + # 所有可用功能 + all_features = [ + {'key': 'basic_translate', 'name': '基础翻译'}, + {'key': 'compare_view', 'name': '对照查看'}, + {'key': 'retranslate', 'name': '重新翻译'}, + {'key': 'history', 'name': '历史记录'}, + {'key': 'priority_queue', 'name': '优先队列'}, + {'key': 'export_pdf', 'name': '导出PDF'}, + {'key': 'batch_translate', 'name': '批量翻译'}, + {'key': 'custom_terms', 'name': '自定义术语'}, + ] + + return render_template('admin/user_type_form.html', user_type=None, all_features=all_features) + + +@admin_bp.route('/user-types//edit', methods=['GET', 'POST']) +@admin_required +def edit_user_type(type_id): + """编辑用户类型""" + user_type = UserTypeConfig.query.get_or_404(type_id) + + if request.method == 'POST': + data = request.json if request.is_json else request.form + + user_type.display_name = data.get('display_name', user_type.display_name) + user_type.daily_translations = int(data.get('daily_translations', user_type.daily_translations)) + user_type.max_pages = int(data.get('max_pages', user_type.max_pages)) + user_type.max_file_size = int(data.get('max_file_size_mb', user_type.max_file_size // 1024 // 1024)) * 1024 * 1024 + user_type.features = data.get('features', user_type.features) + user_type.sort_order = int(data.get('sort_order', user_type.sort_order)) + user_type.is_active = data.get('is_active', True) if isinstance(data.get('is_active'), bool) else data.get('is_active') == 'true' + + db.session.commit() + + # 记录日志 + log = OperationLog( + user_id=session.get('user_id'), + username='admin', + action='edit_user_type', + target=user_type.type_key, + detail=json.dumps(user_type.to_dict(), ensure_ascii=False) + ) + db.session.add(log) + db.session.commit() + + if request.is_json: + return jsonify({'success': True, 'user_type': user_type.to_dict()}) + flash('用户类型已更新', 'success') + return redirect(url_for('admin.user_types')) + + # 所有可用功能 + all_features = [ + {'key': 'basic_translate', 'name': '基础翻译'}, + {'key': 'compare_view', 'name': '对照查看'}, + {'key': 'retranslate', 'name': '重新翻译'}, + {'key': 'history', 'name': '历史记录'}, + {'key': 'priority_queue', 'name': '优先队列'}, + {'key': 'export_pdf', 'name': '导出PDF'}, + {'key': 'batch_translate', 'name': '批量翻译'}, + {'key': 'custom_terms', 'name': '自定义术语'}, + ] + + return render_template('admin/user_type_form.html', user_type=user_type, all_features=all_features) + + +@admin_bp.route('/user-types//delete', methods=['POST']) +@admin_required +def delete_user_type(type_id): + """删除用户类型""" + user_type = UserTypeConfig.query.get_or_404(type_id) + + # 系统内置类型不可删除 + if user_type.is_system: + return jsonify({'error': '系统内置类型不可删除'}), 400 + + # 检查是否有用户使用此类型 + users_count = User.query.filter_by(user_type=user_type.type_key).count() + if users_count > 0: + return jsonify({'error': f'有 {users_count} 个用户使用此类型,请先修改用户类型'}), 400 + + type_key = user_type.type_key + db.session.delete(user_type) + db.session.commit() + + # 记录日志 + log = OperationLog( + user_id=session.get('user_id'), + username='admin', + action='delete_user_type', + target=type_key + ) + db.session.add(log) + db.session.commit() + + return jsonify({'success': True}) + + +@admin_bp.route('/user-types//toggle', methods=['POST']) +@admin_required +def toggle_user_type(type_id): + """切换用户类型状态""" + user_type = UserTypeConfig.query.get_or_404(type_id) + user_type.is_active = not user_type.is_active + db.session.commit() + + return jsonify({'success': True, 'is_active': user_type.is_active}) + + +@admin_bp.route('/user-types/init', methods=['POST']) +@admin_required +def init_user_types(): + """初始化默认用户类型""" + init_default_user_types() + return jsonify({'success': True}) + + +def init_default_user_types(): + """初始化默认用户类型配置""" + from config import USER_LIMITS + + defaults = [ + ('guest', '访客', USER_LIMITS.get('guest', {}), 0, True), + ('free', '免费用户', USER_LIMITS.get('free', {}), 1, True), + ('vip_basic', '基础会员', USER_LIMITS.get('vip_basic', {}), 2, True), + ('vip_pro', '专业会员', USER_LIMITS.get('vip_pro', {}), 3, True), + ('vip_enterprise', '企业会员', USER_LIMITS.get('vip_enterprise', {}), 4, True), + ] + + for type_key, display_name, limits, sort_order, is_system in defaults: + existing = UserTypeConfig.query.filter_by(type_key=type_key).first() + if not existing: + user_type = UserTypeConfig( + type_key=type_key, + display_name=display_name, + daily_translations=limits.get('daily_translations', 10), + max_pages=limits.get('max_pages', 50), + max_file_size=limits.get('max_file_size', 30*1024*1024), + features=json.dumps(limits.get('features', []), ensure_ascii=False), + sort_order=sort_order, + is_active=True, + is_system=is_system, + ) + db.session.add(user_type) + + db.session.commit() + + +# ==================== 会员套餐配置管理(动态增删) ==================== +@admin_bp.route('/membership-plans') +@admin_required +def membership_plans(): + """会员套餐配置列表""" + plans = MembershipPlanConfig.query.order_by(MembershipPlanConfig.sort_order).all() + + # 如果数据库中没有数据,初始化默认配置 + if not plans: + init_default_membership_plans() + plans = MembershipPlanConfig.query.order_by(MembershipPlanConfig.sort_order).all() + + # 获取所有用户类型供选择 + user_types = UserTypeConfig.query.filter_by(is_active=True).all() + + return render_template('admin/membership_plans.html', plans=plans, user_types=user_types) + + +@admin_bp.route('/membership-plans/add', methods=['GET', 'POST']) +@admin_required +def add_membership_plan(): + """添加会员套餐""" + if request.method == 'POST': + data = request.json if request.is_json else request.form + + # 检查plan_key是否已存在 + existing = MembershipPlanConfig.query.filter_by(plan_key=data.get('plan_key')).first() + if existing: + return jsonify({'error': '套餐标识已存在'}), 400 + + plan = MembershipPlanConfig( + plan_key=data.get('plan_key'), + display_name=data.get('display_name'), + price=float(data.get('price', 0)), + original_price=float(data.get('original_price')) if data.get('original_price') else None, + period=data.get('period', 'month'), + period_days=int(data.get('period_days', 30)), + description=data.get('description'), + user_type_key=data.get('user_type_key'), + sort_order=int(data.get('sort_order', 0)), + is_active=data.get('is_active', True), + is_recommended=data.get('is_recommended', False), + is_system=False, + ) + + db.session.add(plan) + db.session.commit() + + # 记录日志 + log = OperationLog( + user_id=session.get('user_id'), + username='admin', + action='add_membership_plan', + target=plan.plan_key, + detail=json.dumps(plan.to_dict(), ensure_ascii=False) + ) + db.session.add(log) + db.session.commit() + + if request.is_json: + return jsonify({'success': True, 'plan': plan.to_dict()}) + flash('会员套餐已添加', 'success') + return redirect(url_for('admin.membership_plans')) + + # 获取所有用户类型供选择 + user_types = UserTypeConfig.query.filter_by(is_active=True).all() + + return render_template('admin/membership_plan_form.html', plan=None, user_types=user_types) + + +@admin_bp.route('/membership-plans//edit', methods=['GET', 'POST']) +@admin_required +def edit_membership_plan(plan_id): + """编辑会员套餐""" + plan = MembershipPlanConfig.query.get_or_404(plan_id) + + if request.method == 'POST': + data = request.json if request.is_json else request.form + + plan.display_name = data.get('display_name', plan.display_name) + plan.price = float(data.get('price', plan.price)) + plan.original_price = float(data.get('original_price')) if data.get('original_price') else None + plan.period = data.get('period', plan.period) + plan.period_days = int(data.get('period_days', plan.period_days)) + plan.description = data.get('description', plan.description) + plan.user_type_key = data.get('user_type_key', plan.user_type_key) + plan.sort_order = int(data.get('sort_order', plan.sort_order)) + plan.is_active = data.get('is_active', True) if isinstance(data.get('is_active'), bool) else data.get('is_active') == 'true' + plan.is_recommended = data.get('is_recommended', False) if isinstance(data.get('is_recommended'), bool) else data.get('is_recommended') == 'true' + + db.session.commit() + + # 记录日志 + log = OperationLog( + user_id=session.get('user_id'), + username='admin', + action='edit_membership_plan', + target=plan.plan_key, + detail=json.dumps(plan.to_dict(), ensure_ascii=False) + ) + db.session.add(log) + db.session.commit() + + if request.is_json: + return jsonify({'success': True, 'plan': plan.to_dict()}) + flash('会员套餐已更新', 'success') + return redirect(url_for('admin.membership_plans')) + + # 获取所有用户类型供选择 + user_types = UserTypeConfig.query.filter_by(is_active=True).all() + + return render_template('admin/membership_plan_form.html', plan=plan, user_types=user_types) + + +@admin_bp.route('/membership-plans//delete', methods=['POST']) +@admin_required +def delete_membership_plan(plan_id): + """删除会员套餐""" + plan = MembershipPlanConfig.query.get_or_404(plan_id) + + # 系统内置套餐不可删除 + if plan.is_system: + return jsonify({'error': '系统内置套餐不可删除'}), 400 + + plan_key = plan.plan_key + db.session.delete(plan) + db.session.commit() + + # 记录日志 + log = OperationLog( + user_id=session.get('user_id'), + username='admin', + action='delete_membership_plan', + target=plan_key + ) + db.session.add(log) + db.session.commit() + + return jsonify({'success': True}) + + +@admin_bp.route('/membership-plans//toggle', methods=['POST']) +@admin_required +def toggle_membership_plan(plan_id): + """切换会员套餐状态""" + plan = MembershipPlanConfig.query.get_or_404(plan_id) + plan.is_active = not plan.is_active + db.session.commit() + + return jsonify({'success': True, 'is_active': plan.is_active}) + + +@admin_bp.route('/membership-plans//recommend', methods=['POST']) +@admin_required +def recommend_membership_plan(plan_id): + """设置推荐套餐""" + plan = MembershipPlanConfig.query.get_or_404(plan_id) + plan.is_recommended = not plan.is_recommended + db.session.commit() + + return jsonify({'success': True, 'is_recommended': plan.is_recommended}) + + +@admin_bp.route('/membership-plans/init', methods=['POST']) +@admin_required +def init_membership_plans(): + """初始化默认会员套餐""" + init_default_membership_plans() + return jsonify({'success': True}) + + +def init_default_membership_plans(): + """初始化默认会员套餐配置""" + from config import MEMBERSHIP_PLANS + + defaults = [ + ('vip_basic', MEMBERSHIP_PLANS.get('vip_basic', {}), 'vip_basic', 0, True), + ('vip_pro', MEMBERSHIP_PLANS.get('vip_pro', {}), 'vip_pro', 1, True), + ('vip_enterprise', MEMBERSHIP_PLANS.get('vip_enterprise', {}), 'vip_enterprise', 2, True), + ] + + for plan_key, plan_data, user_type_key, sort_order, is_system in defaults: + existing = MembershipPlanConfig.query.filter_by(plan_key=plan_key).first() + if not existing: + plan = MembershipPlanConfig( + plan_key=plan_key, + display_name=plan_data.get('name', plan_key), + price=plan_data.get('price', 0), + original_price=None, + period=plan_data.get('period', 'month'), + period_days=30 if plan_data.get('period') == 'month' else 365 if plan_data.get('period') == 'year' else 90, + description=plan_data.get('description', ''), + user_type_key=user_type_key, + sort_order=sort_order, + is_active=True, + is_recommended=False, + is_system=is_system, + ) + db.session.add(plan) + + db.session.commit() + + +# ==================== API: 获取用户限制配置(供其他模块使用) ==================== +def get_user_limits(user_type_key): + """获取指定用户类型的限制配置""" + config = UserTypeConfig.query.filter_by(type_key=user_type_key, is_active=True).first() + if config: + return { + 'daily_translations': config.daily_translations, + 'max_pages': config.max_pages, + 'max_file_size': config.max_file_size, + 'features': config.get_features(), + } + # 如果数据库中没有,使用默认配置 + from config import USER_LIMITS + return USER_LIMITS.get(user_type_key, USER_LIMITS.get('free', {})) + + +def get_all_user_types(): + """获取所有用户类型配置""" + types = UserTypeConfig.query.filter_by(is_active=True).order_by(UserTypeConfig.sort_order).all() + return {t.type_key: get_user_limits(t.type_key) for t in types} + + +def get_membership_plan(plan_key): + """获取指定会员套餐配置""" + plan = MembershipPlanConfig.query.filter_by(plan_key=plan_key, is_active=True).first() + if plan: + return plan.to_dict() + from config import MEMBERSHIP_PLANS + return MEMBERSHIP_PLANS.get(plan_key, {}) + + +def get_all_membership_plans(): + """获取所有会员套餐配置""" + plans = MembershipPlanConfig.query.filter_by(is_active=True).order_by(MembershipPlanConfig.sort_order).all() + return [p.to_dict() for p in plans] \ No newline at end of file diff --git a/models.py b/models.py index a4002b7..e1557f9 100644 --- a/models.py +++ b/models.py @@ -417,4 +417,107 @@ class DynamicConfig(db.Model): db.session.add(config) db.session.commit() - return config \ No newline at end of file + return config + + +# ==================== 用户类型配置(动态增删) ==================== +class UserTypeConfig(db.Model): + """用户类型配置表 - 支持动态增删""" + __tablename__ = 'user_type_config' + + id = db.Column(db.Integer, primary_key=True) + type_key = db.Column(db.String(50), unique=True, nullable=False) # 类型标识: guest, free, vip_basic, etc. + display_name = db.Column(db.String(100), nullable=False) # 显示名称: 访客, 免费用户, 基础会员 + + # 权限配置 + daily_translations = db.Column(db.Integer, default=10) # 每日翻译次数 (-1=无限) + max_pages = db.Column(db.Integer, default=50) # 最大页数 (-1=无限) + max_file_size = db.Column(db.Integer, default=30*1024*1024) # 最大文件大小(bytes) + + # 功能列表(JSON) + features = db.Column(db.Text, default='[]') # JSON数组: ["basic_translate", "history"] + + # 排序和状态 + sort_order = db.Column(db.Integer, default=0) # 排序权重 + is_active = db.Column(db.Boolean, default=True) # 是否启用 + is_system = db.Column(db.Boolean, default=False) # 是否系统内置(不可删除) + + # 时间 + created_at = db.Column(db.DateTime, default=datetime.utcnow) + updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) + + def get_features(self): + """获取功能列表""" + import json + try: + return json.loads(self.features) if self.features else [] + except: + return [] + + def set_features(self, feature_list): + """设置功能列表""" + import json + self.features = json.dumps(feature_list, ensure_ascii=False) + + def to_dict(self): + return { + 'id': self.id, + 'type_key': self.type_key, + 'display_name': self.display_name, + 'daily_translations': self.daily_translations, + 'max_pages': self.max_pages, + 'max_file_size': self.max_file_size, + 'features': self.get_features(), + 'sort_order': self.sort_order, + 'is_active': self.is_active, + 'is_system': self.is_system, + } + + +# ==================== 会员套餐配置(动态增删) ==================== +class MembershipPlanConfig(db.Model): + """会员套餐配置表 - 支持动态增删""" + __tablename__ = 'membership_plan_config' + + id = db.Column(db.Integer, primary_key=True) + plan_key = db.Column(db.String(50), unique=True, nullable=False) # 套餐标识: vip_basic, vip_pro, etc. + display_name = db.Column(db.String(100), nullable=False) # 显示名称: 基础会员 + + # 价格配置 + price = db.Column(db.Float, default=0) # 价格 + original_price = db.Column(db.Float, nullable=True) # 原价(用于折扣显示) + period = db.Column(db.String(20), default='month') # 周期: month, quarter, year + period_days = db.Column(db.Integer, default=30) # 周期天数 + + # 描述 + description = db.Column(db.String(255), nullable=True) # 套餐描述 + + # 对应的用户类型 + user_type_key = db.Column(db.String(50), nullable=True) # 购买后升级到的用户类型 + + # 排序和状态 + sort_order = db.Column(db.Integer, default=0) + is_active = db.Column(db.Boolean, default=True) + is_recommended = db.Column(db.Boolean, default=False) # 是否推荐 + is_system = db.Column(db.Boolean, default=False) # 是否系统内置(不可删除) + + # 时间 + created_at = db.Column(db.DateTime, default=datetime.utcnow) + updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) + + def to_dict(self): + return { + 'id': self.id, + 'plan_key': self.plan_key, + 'display_name': self.display_name, + 'price': self.price, + 'original_price': self.original_price, + 'period': self.period, + 'period_days': self.period_days, + 'description': self.description, + 'user_type_key': self.user_type_key, + 'sort_order': self.sort_order, + 'is_active': self.is_active, + 'is_recommended': self.is_recommended, + 'is_system': self.is_system, + } \ No newline at end of file diff --git a/templates/admin/membership_plan_form.html b/templates/admin/membership_plan_form.html new file mode 100644 index 0000000..82230af --- /dev/null +++ b/templates/admin/membership_plan_form.html @@ -0,0 +1,240 @@ + + + + + {% if plan %}编辑会员套餐{% else %}添加会员套餐{% endif %} - 后台管理 + + + + + + + +
+
+

+ + {% if plan %}编辑会员套餐{% else %}添加会员套餐{% endif %} +

+ + 返回列表 + +
+ +
+
+
+ {% if plan %} + + {% endif %} + +
+
+
+ + {% if plan %} + + 套餐标识创建后不可修改 + {% else %} + + 建议使用英文,如 vip_gold, trial_30days + {% endif %} +
+ +
+ + +
+ +
+ + +
+ +
+ + + 用户购买此套餐后将升级到指定类型 +
+
+ +
+
+
+
+ + +
+
+
+
+ + +
+
+
+ +
+
+
+ + +
+
+
+
+ + +
+
+
+ +
+ + + 数字越小排在前面 +
+ +
+
+ + +
+
+ +
+
+ + +
+
+
+
+ +
+ + + 取消 + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/templates/admin/membership_plans.html b/templates/admin/membership_plans.html new file mode 100644 index 0000000..5e237cf --- /dev/null +++ b/templates/admin/membership_plans.html @@ -0,0 +1,221 @@ + + + + + 会员套餐配置 - 后台管理 + + + + + + + +
+ + +
+ {% for plan in plans %} +
+
+
+
+ {% if plan.is_recommended %} + 推荐 + {% endif %} + {% if plan.is_system %} + 系统 + {% else %} + 自定义 + {% endif %} + {{ plan.display_name }} +
+ + {% if plan.is_active %}上架{% else %}下架{% endif %} + +
+
+
+ {% if plan.original_price and plan.original_price > plan.price %} + + ¥{{ plan.original_price }} + + {% endif %} + ¥{{ plan.price }} +
+
+ / {% if plan.period == 'month' %}月{% elif plan.period == 'quarter' %}季{% elif plan.period == 'year' %}年{% else %}{{ plan.period_days }}天{% endif %} +
+ + {% if plan.description %} +

{{ plan.description }}

+ {% endif %} + + {% if plan.user_type_key %} +
+ 购买后升级为: + {{ plan.user_type_key }} +
+ {% endif %} + + + + + + + + + + +
套餐标识{{ plan.plan_key }}
周期天数{{ plan.period_days }} 天
+ +
+ + 编辑 + + {% if not plan.is_system %} + + {% endif %} + + +
+
+
+
+ {% endfor %} +
+ + {% if not plans %} +
+
+

数据库中暂无会员套餐配置

+ +
+
+ {% endif %} +
+ + + + \ No newline at end of file diff --git a/templates/admin/settings.html b/templates/admin/settings.html index be586c5..aaaaaf7 100644 --- a/templates/admin/settings.html +++ b/templates/admin/settings.html @@ -11,6 +11,8 @@ .sidebar .nav-link { color: #adb5bd; padding: 12px 20px; } .sidebar .nav-link:hover, .sidebar .nav-link.active { color: #fff; background: rgba(255,255,255,0.1); } .main-content { margin-left: 250px; padding: 20px; } + .config-card { border-radius: 10px; transition: all 0.3s; } + .config-card:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0,0,0,0.1); } @@ -23,6 +25,7 @@ + @@ -34,79 +37,101 @@
-
+

系统配置

+ +
-
-
用户权限配置
+
+
+
用户类型配置
+
- - - - - - - - - - {% 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 文件 +

配置会员套餐的价格、周期、描述等,用户购买后可升级用户类型。

+

支持操作:

+
    +
  • 添加新的会员套餐
  • +
  • 编辑套餐价格和描述
  • +
  • 删除自定义套餐(系统套餐不可删除)
  • +
  • 上架/下架套餐
  • +
  • 设置推荐套餐
  • +
+ + 管理会员套餐 +
-
+
+
+
+
+
数据包套餐
+
+
+

管理数据包套餐,用户可以购买额外的翻译次数。

+ + 管理数据包 + +
+
+
+ +
+
+
+
大模型配置
+
+
+

配置翻译使用的LLM大模型API地址、模型名称等参数。

+

当前模型: {{ llm_config.model }}

+ + 配置大模型 + +
+
+
+
+ +
系统信息

应用名称: PDF翻译助手

-

版本: 1.0.0

+

版本: 2.0.0

框架: Flask + SQLAlchemy

-

LLM模型: {{ llm_config.model }}

-

API地址: {{ llm_config.api_base }}

-

修改配置

+

缓存有效期: 30天

+

默认最大文件: 50MB

+

数据库: SQLite

-

缓存有效期: 30天

-

最大文件: 50MB

+

API地址: {{ llm_config.api_base }}

+

超时时间: {{ llm_config.timeout }}秒

diff --git a/templates/admin/user_type_form.html b/templates/admin/user_type_form.html new file mode 100644 index 0000000..7adce1a --- /dev/null +++ b/templates/admin/user_type_form.html @@ -0,0 +1,198 @@ + + + + + {% if user_type %}编辑用户类型{% else %}添加用户类型{% endif %} - 后台管理 + + + + + + + +
+
+

+ + {% if user_type %}编辑用户类型{% else %}添加用户类型{% endif %} +

+ + 返回列表 + +
+ +
+
+
+ {% if user_type %} + + {% endif %} + +
+
+
+ + {% if user_type %} + + 类型标识创建后不可修改 + {% else %} + + 建议使用英文,如 vip_special, trial_user + {% endif %} +
+ +
+ + +
+ +
+ + + 数字越小排在前面 +
+
+ +
+
+ + + 输入 -1 表示无限制 +
+ +
+ + + 输入 -1 表示无限制 +
+ +
+ + +
+ +
+ +
+ + +
+
+
+
+ +
+ +
+ {% for feature in all_features %} +
+
+ + +
+
+ {% endfor %} +
+
+ +
+ + + 取消 + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/templates/admin/user_types.html b/templates/admin/user_types.html new file mode 100644 index 0000000..e8d0edb --- /dev/null +++ b/templates/admin/user_types.html @@ -0,0 +1,206 @@ + + + + + 用户类型配置 - 后台管理 + + + + + + + +
+ + +
+ {% for user_type in user_types %} +
+
+
+
+ + {% if user_type.is_system %}系统{% else %}自定义{% endif %} + + {{ user_type.display_name }} +
+ + {% if user_type.is_active %}启用{% else %}禁用{% endif %} + +
+
+ + + + + + + + + + + + + + + + + +
类型标识{{ user_type.type_key }}
每日翻译 + {% if user_type.daily_translations < 0 %} + 无限制 + {% else %} + {{ user_type.daily_translations }} 次 + {% endif %} +
最大页数 + {% if user_type.max_pages < 0 %} + 无限制 + {% else %} + {{ user_type.max_pages }} 页 + {% endif %} +
最大文件{{ (user_type.max_file_size / 1024 / 1024) | round(0) }} MB
+ +
+ 功能权限: +
+
+ {% for feature in user_type.get_features() %} + {{ feature }} + {% endfor %} + {% if not user_type.get_features() %} + 无特殊功能 + {% endif %} +
+ +
+ + 编辑 + + {% if not user_type.is_system %} + + {% endif %} + +
+
+
+
+ {% endfor %} +
+ + {% if not user_types %} +
+
+

数据库中暂无用户类型配置

+ +
+
+ {% endif %} +
+ + + + \ No newline at end of file