2 Commits

Author SHA1 Message Date
1d36888488 feat: 套餐权益列表从后台UserTypeConfig动态读取
- 权益列表从数据库配置读取
- 支持按用户类型显示/状态
- 更新各等级用户权益配置
2026-04-14 18:42:21 +08:00
ee5e672901 feat: pricing页面使用后台管理配置的会员套餐数据
- 从数据库MembershipPlanConfig读取动态配置
- 支持推荐标记、原价显示
- 按用户状态显示按钮
2026-04-14 18:39:04 +08:00
2 changed files with 93 additions and 74 deletions

69
app.py
View File

@@ -15,7 +15,7 @@ from werkzeug.utils import secure_filename
from config import * from config import *
from models import (db, User, Translation, TranslationCache, GuestTranslation, from models import (db, User, Translation, TranslationCache, GuestTranslation,
DataPackage, UserPackage, DynamicConfig, UserRecharge, UserRefund, DataPackage, UserPackage, DynamicConfig, UserRecharge, UserRefund,
MembershipPurchase, AccountTransaction) MembershipPurchase, AccountTransaction, MembershipPlanConfig, UserTypeConfig)
from services import TranslationService, CacheService, TranslationTask from services import TranslationService, CacheService, TranslationTask
from admin import admin_bp from admin import admin_bp
@@ -208,7 +208,72 @@ def history():
def pricing(): def pricing():
"""会员定价页""" """会员定价页"""
user = get_current_user() user = get_current_user()
return render_template('pricing.html', plans=MEMBERSHIP_PLANS, user=user)
# 权益名称映射
feature_names = {
'basic_translate': '基础翻译功能',
'history': '翻译历史记录',
'retranslate': '不满意重新翻译',
'export_pdf': '导出PDF格式',
'compare_view': '原文译文对比查看',
'batch_translate': '批量翻译',
'custom_terms': '自定义术语库',
'priority_queue': '优先处理队列',
'custom_instruction': '自定义翻译要求',
'api_access': 'API接口调用',
}
# 从数据库读取动态配置的会员套餐
db_plans = MembershipPlanConfig.query.filter_by(is_active=True)\
.order_by(MembershipPlanConfig.sort_order).all()
# 读取用户类型配置获取权益
user_types = UserTypeConfig.query.filter_by(is_active=True).all()
user_type_map = {ut.type_key: ut for ut in user_types}
# 为每个套餐添加权益列表
plan_features = {}
for plan in db_plans:
ut = user_type_map.get(plan.user_type_key)
if ut:
features = ut.get_features()
plan_features[plan.plan_key] = [
{'key': f, 'name': feature_names.get(f, f), 'has': True}
for f in features
]
# 添加限制信息
plan_features[plan.plan_key].insert(0, {
'key': 'daily_translations',
'name': f"每日翻译{ut.daily_translations if ut.daily_translations > 0 else '无限'}",
'has': True
})
plan_features[plan.plan_key].insert(1, {
'key': 'max_pages',
'name': f"单文件最大{ut.max_pages if ut.max_pages > 0 else '无限'}",
'has': True
})
# 免费用户权益
free_ut = user_type_map.get('free')
free_features = []
if free_ut:
free_features = [
{'key': 'daily_translations', 'name': f"每日翻译{free_ut.daily_translations}", 'has': True},
{'key': 'max_pages', 'name': f"单文件最大{free_ut.max_pages}", 'has': True},
]
for f in free_ut.get_features():
free_features.append({'key': f, 'name': feature_names.get(f, f), 'has': True})
# 添加没有的功能
all_features = ['compare_view', 'batch_translate', 'custom_terms']
for f in all_features:
if f not in free_ut.get_features():
free_features.append({'key': f, 'name': feature_names.get(f, f), 'has': False})
return render_template('pricing.html',
plans=db_plans,
plan_features=plan_features,
free_features=free_features,
user=user)
@app.route('/profile') @app.route('/profile')

View File

@@ -41,7 +41,7 @@
<h2 class="text-center mb-5">会员套餐</h2> <h2 class="text-center mb-5">会员套餐</h2>
<div class="row justify-content-center"> <div class="row justify-content-center">
<!-- 免费用户 --> <!-- 免费用户(固定) -->
<div class="col-lg-3 col-md-6 mb-4"> <div class="col-lg-3 col-md-6 mb-4">
<div class="card pricing-card h-100"> <div class="card pricing-card h-100">
<div class="card-body text-center"> <div class="card-body text-center">
@@ -49,13 +49,9 @@
<div class="price my-3">¥0<small>/永久</small></div> <div class="price my-3">¥0<small>/永久</small></div>
<ul class="features text-start"> <ul class="features text-start">
<li>✅ 每日翻译10次</li> {% for feat in free_features %}
<li>✅ 单文件最大50页</li> <li>{% if feat.has %}✅{% else %}<span class="text-muted"></span>{% endif %} {{ feat.name }}</li>
<li>✅ 翻译历史记录</li> {% endfor %}
<li>✅ 不满意重新翻译</li>
<li>✅ 导出PDF格式</li>
<li class="text-muted">❌ 原文译文对比</li>
<li class="text-muted">❌ 批量翻译</li>
</ul> </ul>
{% if user and user.user_type in ['free', 'vip_basic', 'vip_pro', 'vip_enterprise', 'admin'] %} {% if user and user.user_type in ['free', 'vip_basic', 'vip_pro', 'vip_enterprise', 'admin'] %}
@@ -69,87 +65,45 @@
</div> </div>
</div> </div>
<!-- 基础会员 --> <!-- 动态套餐 -->
{% for plan in db_plans if plan.is_active %}
<div class="col-lg-3 col-md-6 mb-4"> <div class="col-lg-3 col-md-6 mb-4">
<div class="card pricing-card h-100"> <div class="card pricing-card h-100 {% if plan.is_recommended %}border-primary{% endif %}">
<div class="card-body text-center"> {% if plan.is_recommended %}
<h4 class="card-title">基础会员</h4>
<div class="price my-3">¥29<small>/月</small></div>
<ul class="features text-start">
<li>✅ 每日翻译50次</li>
<li>✅ 单文件最大100页</li>
<li>✅ 翻译历史记录</li>
<li>✅ 不满意重新翻译</li>
<li>✅ 原文译文对比查看</li>
<li>✅ 导出PDF格式</li>
<li>✅ 优先处理队列</li>
</ul>
{% if user and user.user_type == 'vip_basic' %}
<span class="btn btn-success w-100 mt-3 disabled">当前套餐</span>
{% elif user and user.user_type in ['vip_pro', 'vip_enterprise', 'admin'] %}
<span class="btn btn-secondary w-100 mt-3 disabled">已升级</span>
{% else %}
<button class="btn btn-outline-primary w-100 mt-3">立即购买</button>
{% endif %}
</div>
</div>
</div>
<!-- 专业会员 -->
<div class="col-lg-3 col-md-6 mb-4">
<div class="card pricing-card h-100 border-primary">
<div class="card-header bg-primary text-white text-center"> <div class="card-header bg-primary text-white text-center">
<strong>推荐</strong> <strong>推荐</strong>
</div> </div>
{% endif %}
<div class="card-body text-center"> <div class="card-body text-center">
<h4 class="card-title">专业会员</h4> <h4 class="card-title">{{ plan.display_name }}</h4>
<div class="price my-3">¥99<small>/月</small></div> <div class="price my-3">
¥{{ plan.price }}<small>/{{ plan.period }}</small>
{% if plan.original_price and plan.original_price > plan.price %}
<br><span class="text-muted" style="font-size:0.8em">原价¥{{ plan.original_price }}</span>
{% endif %}
</div>
<p class="text-muted">{{ plan.description }}</p>
<ul class="features text-start"> <ul class="features text-start">
<li>✅ 每日翻译200次</li> {% for feat in plan_features.get(plan.plan_key, []) %}
<li>单文件最大500页</li> <li>{{ feat.name }}</li>
<li>✅ 所有基础会员功能</li> {% endfor %}
<li>✅ 原文译文对比查看</li>
<li>✅ 批量翻译</li>
<li>✅ 自定义术语库</li>
</ul> </ul>
{% if user and user.user_type == 'vip_pro' %} {% if user and user.user_type == plan.user_type_key %}
<span class="btn btn-success w-100 mt-3 disabled">当前套餐</span> <span class="btn btn-success w-100 mt-3 disabled">当前套餐</span>
{% elif user and user.user_type in ['vip_enterprise', 'admin'] %} {% elif user and user.user_type in ['vip_enterprise', 'admin'] %}
<span class="btn btn-secondary w-100 mt-3 disabled">已升级</span> <span class="btn btn-secondary w-100 mt-3 disabled">已升级</span>
{% elif user and user.user_type in ['vip_pro', 'vip_basic'] and plan.user_type_key not in ['vip_basic'] %}
<span class="btn btn-secondary w-100 mt-3 disabled">已升级</span>
{% else %} {% else %}
<button class="btn btn-primary w-100 mt-3">立即购买</button> <button class="btn {% if plan.is_recommended %}btn-primary{% else %}btn-outline-primary{% endif %} w-100 mt-3">立即购买</button>
{% endif %}
</div>
</div>
</div>
<!-- 企业会员 -->
<div class="col-lg-3 col-md-6 mb-4">
<div class="card pricing-card h-100">
<div class="card-body text-center">
<h4 class="card-title">企业会员</h4>
<div class="price my-3">¥999<small>/年</small></div>
<ul class="features text-start">
<li>✅ 翻译次数无限制</li>
<li>✅ 页数无限制</li>
<li>✅ 所有功能解锁</li>
<li>✅ 专属客服支持</li>
<li>✅ API接口调用</li>
</ul>
{% if user and user.user_type in ['vip_enterprise', 'admin'] %}
<span class="btn btn-success w-100 mt-3 disabled">当前套餐</span>
{% else %}
<button class="btn btn-outline-primary w-100 mt-3">联系购买</button>
{% endif %} {% endif %}
</div> </div>
</div> </div>
</div> </div>
{% endfor %}
</div> </div>
<!-- 功能对比表 --> <!-- 功能对比表 -->