feat: 添加手机号、邀请好友、邮件通知功能

- 用户模型添加:手机号、邀请码、邀请统计、邮件通知设置
- 邀请好友系统:专属邀请码、邀请奖励(¥5/人)
- 邮件通知:翻译完成通知(含附件)、欢迎邮件、到期提醒
- 新增模型:UserInvitation, InviteRewardConfig, EmailNotification, EmailTemplateConfig
- 个人中心添加:手机号绑定、通知设置、邀请好友模块
- email_service.py:邮件发送服务(支持附件)

新用户注册奖励:¥2
邀请人奖励:¥5/人
This commit is contained in:
2026-04-14 18:58:40 +08:00
parent 4aac8ab04c
commit 71a613ff5f
4 changed files with 725 additions and 4 deletions

138
app.py
View File

@@ -15,7 +15,9 @@ from werkzeug.utils import secure_filename
from config import *
from models import (db, User, Translation, TranslationCache, GuestTranslation,
DataPackage, UserPackage, DynamicConfig, UserRecharge, UserRefund,
MembershipPurchase, AccountTransaction, MembershipPlanConfig, UserTypeConfig)
MembershipPurchase, AccountTransaction, MembershipPlanConfig, UserTypeConfig,
UserInvitation, InviteRewardConfig, EmailNotification, EmailTemplateConfig)
from email_service import email_service
from services import TranslationService, CacheService, TranslationTask
from admin import admin_bp
@@ -591,12 +593,15 @@ def login():
def register():
"""注册"""
if request.method == 'GET':
return render_template('register.html')
# 检查邀请码参数
invite_code = request.args.get('invite', None)
return render_template('register.html', invite_code=invite_code)
data = request.json
username = data.get('username')
email = data.get('email')
password = data.get('password')
invite_code = data.get('invite_code', None) # 邀请码
# 检查用户是否存在
if User.query.filter_by(username=username).first():
@@ -608,11 +613,76 @@ def register():
# 创建用户
user = User(username=username, email=email, user_type='free')
user.set_password(password)
# 生成邀请码6位随机字符
import random
import string
user.invite_code = ''.join(random.choices(string.ascii_uppercase + string.digits, k=6))
# 处理邀请
if invite_code:
inviter = User.query.filter_by(invite_code=invite_code).first()
if inviter:
user.invited_by = inviter.id
# 创建邀请记录
invitation = UserInvitation(
inviter_id=inviter.id,
invite_code=invite_code,
invitee_id=user.id,
invitee_email=email,
reward_amount=5.0,
status='registered',
registered_at=datetime.utcnow()
)
db.session.add(invitation)
# 给邀请人奖励
inviter.invite_count += 1
inviter.invite_rewards += 5.0
inviter.balance += 5.0
# 给被邀请人奖励
user.balance += 2.0
# 创建流水
inviter_tx = AccountTransaction(
user_id=inviter.id,
transaction_type='invite_reward',
amount=5.0,
balance_before=inviter.balance - 5.0,
balance_after=inviter.balance,
description=f'邀请用户{username}注册奖励'
)
db.session.add(inviter_tx)
user_tx = AccountTransaction(
user_id=user.id,
transaction_type='invite_bonus',
amount=2.0,
balance_before=0,
balance_after=2.0,
description='新用户注册奖励'
)
db.session.add(user_tx)
# 发送邀请奖励邮件
if inviter.email_notify:
email_service.send_invite_reward(inviter.email, inviter.username, 5.0, inviter.invite_count)
db.session.add(user)
db.session.commit()
session['user_id'] = user.id
return jsonify({'success': True, 'user': user.to_dict()})
# 发送欢迎邮件
email_service.send_welcome_email(user.email, user.username, user.invite_code)
return jsonify({
'success': True,
'user': user.to_dict(),
'invite_reward': user.balance > 0 # 是否获得邀请奖励
})
@app.route('/logout')
@@ -775,6 +845,68 @@ def request_refund():
})
@app.route('/api/profile/settings', methods=['POST'])
def update_settings():
"""更新账户设置"""
user = get_current_user()
if not user:
return jsonify({'error': '请登录'}), 401
data = request.json
# 手机号
if 'phone' in data:
phone = data.get('phone', '')
if phone and len(phone) >= 10:
user.phone = phone
user.phone_verified = False
# 通知设置
if 'notify_on_complete' in data:
user.notify_on_complete = data.get('notify_on_complete', True)
if 'notify_on_expire' in data:
user.notify_on_expire = data.get('notify_on_expire', True)
if 'email_notify' in data:
user.email_notify = data.get('email_notify', True)
db.session.commit()
return jsonify({
'success': True,
'user': user.to_dict()
})
@app.route('/api/profile/invitations')
def get_invitations():
"""获取邀请记录"""
user = get_current_user()
if not user:
return jsonify({'error': '请登录'}), 401
invitations = UserInvitation.query.filter_by(inviter_id=user.id)\
.order_by(UserInvitation.created_at.desc()).limit(20).all()
return jsonify({
'success': True,
'invitations': [inv.to_dict() for inv in invitations]
})
@app.route('/api/invite/<invite_code>')
def check_invite(invite_code):
"""检查邀请码"""
inviter = User.query.filter_by(invite_code=invite_code).first()
if not inviter:
return jsonify({'valid': False, 'error': '邀请码无效'})
return jsonify({
'valid': True,
'inviter': inviter.username,
'reward': 5 # 被邀请人奖励
})
# ==================== 初始化 ====================
def init_app():
"""初始化应用"""

210
email_service.py Normal file
View File

@@ -0,0 +1,210 @@
"""
邮件发送服务
支持SMTP邮件发送、附件发送、模板渲染
"""
import os
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from email import encoders
from datetime import datetime
from flask import current_app
class EmailService:
"""邮件发送服务"""
def __init__(self, smtp_host=None, smtp_port=None, smtp_user=None, smtp_pass=None):
self.smtp_host = smtp_host or os.environ.get('SMTP_HOST', 'mail.tphai.com')
self.smtp_port = int(smtp_port or os.environ.get('SMTP_PORT', '587'))
self.smtp_user = smtp_user or os.environ.get('SMTP_USER', 'favor@tphai.com')
self.smtp_pass = smtp_pass or os.environ.get('SMTP_PASS', 'favor@!')
def send_email(self, to_email, subject, body, attachment_path=None, attachment_name=None):
"""发送邮件(支持附件)"""
try:
# 创建邮件对象
msg = MIMEMultipart()
msg['From'] = self.smtp_user
msg['To'] = to_email
msg['Subject'] = subject
msg['Date'] = datetime.now().strftime('%a, %d %b %Y %H:%M:%S +0800')
msg['Reply-To'] = to_email
# 正文
msg.attach(MIMEText(body, 'html', 'utf-8'))
# 附件
if attachment_path and os.path.exists(attachment_path):
with open(attachment_path, 'rb') as f:
part = MIMEBase('application', 'octet-stream')
part.set_payload(f.read())
encoders.encode_base64(part)
part.add_header('Content-Disposition', 'attachment',
filename=attachment_name or os.path.basename(attachment_path))
msg.attach(part)
# 发送
server = smtplib.SMTP(self.smtp_host, self.smtp_port)
server.ehlo()
server.login(self.smtp_user, self.smtp_pass)
server.sendmail(self.smtp_user, to_email, msg.as_string())
server.quit()
return True, "发送成功"
except Exception as e:
return False, str(e)
def send_translation_complete(self, user_email, username, filename, output_path, translation_id):
"""翻译完成通知"""
subject = f"【PDF翻译助手】翻译完成 - {filename}"
body = f"""
<html>
<body style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; padding: 20px;">
<div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 30px; text-align: center; border-radius: 10px 10px 0 0;">
<h1 style="color: white; margin: 0;">📄 PDF翻译助手</h1>
</div>
<div style="background: white; padding: 30px; border: 1px solid #eee; border-radius: 0 0 10px 10px;">
<p style="color: #333; font-size: 16px;">您好,{username}</p>
<p style="color: #666;">您的翻译任务已完成:</p>
<div style="background: #f8f9fa; padding: 15px; border-radius: 5px; margin: 20px 0;">
<p style="margin: 5px 0;"><strong>文件:</strong>{filename}</p>
<p style="margin: 5px 0;"><strong>状态:</strong><span style="color: #28a745;">✅ 完成</span></p>
<p style="margin: 5px 0;"><strong>时间:</strong>{datetime.now().strftime('%Y-%m-%d %H:%M')}</p>
</div>
<p style="color: #666;">翻译结果已作为附件发送,您也可以登录网站查看详情。</p>
<div style="text-align: center; margin: 30px 0;">
<a href="http://localhost:19000/translation/{translation_id}"
style="background: #667eea; color: white; padding: 12px 30px; text-decoration: none; border-radius: 5px;">
查看翻译结果
</a>
</div>
<hr style="border: none; border-top: 1px solid #eee; margin: 30px 0;">
<p style="color: #999; font-size: 12px; text-align: center;">
此邮件由系统自动发送,请勿回复。<br>
PDF翻译助手 - 让翻译更简单
</p>
</div>
</body>
</html>
"""
# 附件名称
attachment_name = f"{filename}_translated.md"
return self.send_email(user_email, subject, body, output_path, attachment_name)
def send_welcome_email(self, user_email, username, invite_code=None):
"""欢迎邮件"""
subject = "欢迎加入PDF翻译助手"
body = f"""
<html>
<body style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; padding: 20px;">
<div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 30px; text-align: center; border-radius: 10px 10px 0 0;">
<h1 style="color: white; margin: 0;">📄 PDF翻译助手</h1>
</div>
<div style="background: white; padding: 30px; border: 1px solid #eee; border-radius: 0 0 10px 10px;">
<p style="color: #333; font-size: 16px;">您好,{username}</p>
<p style="color: #666;">欢迎加入PDF翻译助手您已获得</p>
<div style="background: #f8f9fa; padding: 15px; border-radius: 5px; margin: 20px 0;">
<p style="margin: 5px 0;">✅ 每日免费翻译 10 次</p>
<p style="margin: 5px 0;">✅ 单文件最大 50 页</p>
<p style="margin: 5px 0;">✅ 翻译历史记录</p>
<p style="margin: 5px 0;">✅ 不满意重新翻译</p>
</div>
{"<p style='color: #666;'>您的专属邀请码:<strong style='color: #667eea;'>" + invite_code + "</strong>,分享给好友可获得奖励!</p>" if invite_code else ""}
<div style="text-align: center; margin: 30px 0;">
<a href="http://localhost:19000/"
style="background: #667eea; color: white; padding: 12px 30px; text-decoration: none; border-radius: 5px;">
开始使用
</a>
</div>
</div>
</body>
</html>
"""
return self.send_email(user_email, subject, body)
def send_expire_reminder(self, user_email, username, expire_date, user_type):
"""会员到期提醒"""
subject = "【PDF翻译助手】会员即将到期提醒"
body = f"""
<html>
<body style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; padding: 20px;">
<div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 30px; text-align: center; border-radius: 10px 10px 0 0;">
<h1 style="color: white; margin: 0;">📄 PDF翻译助手</h1>
</div>
<div style="background: white; padding: 30px; border: 1px solid #eee; border-radius: 0 0 10px 10px;">
<p style="color: #333; font-size: 16px;">您好,{username}</p>
<div style="background: #fff3cd; padding: 15px; border-radius: 5px; margin: 20px 0; border-left: 4px solid #ffc107;">
<p style="margin: 5px 0; color: #856404;">⚠️ 您的会员即将到期</p>
<p style="margin: 5px 0;"><strong>会员类型:</strong>{user_type}</p>
<p style="margin: 5px 0;"><strong>到期时间:</strong>{expire_date}</p>
</div>
<p style="color: #666;">到期后将降级为免费用户每日翻译次数限制为10次。续费可继续享受会员权益。</p>
<div style="text-align: center; margin: 30px 0;">
<a href="http://localhost:19000/pricing"
style="background: #667eea; color: white; padding: 12px 30px; text-decoration: none; border-radius: 5px;">
续费会员
</a>
</div>
</div>
</body>
</html>
"""
return self.send_email(user_email, subject, body)
def send_invite_reward(self, user_email, username, reward_amount, invitee_count):
"""邀请奖励通知"""
subject = f"【PDF翻译助手】邀请奖励 - ¥{reward_amount}"
body = f"""
<html>
<body style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; padding: 20px;">
<div style="background: linear-gradient(135deg, #28a745 0%, #20c997 100%); padding: 30px; text-align: center; border-radius: 10px 10px 0 0;">
<h1 style="color: white; margin: 0;">🎉 邀请奖励已发放</h1>
</div>
<div style="background: white; padding: 30px; border: 1px solid #eee; border-radius: 0 0 10px 10px;">
<p style="color: #333; font-size: 16px;">您好,{username}</p>
<div style="background: #d4edda; padding: 15px; border-radius: 5px; margin: 20px 0; border-left: 4px solid #28a745;">
<p style="margin: 5px 0; color: #155724;"><strong>奖励金额:</strong>¥{reward_amount}</p>
<p style="margin: 5px 0; color: #155724;"><strong>累计邀请:</strong>{invitee_count}人</p>
</div>
<p style="color: #666;">奖励已发放到您的账户余额,可用于购买会员或翻译服务。</p>
<div style="text-align: center; margin: 30px 0;">
<a href="http://localhost:19000/profile"
style="background: #28a745; color: white; padding: 12px 30px; text-decoration: none; border-radius: 5px;">
查看余额
</a>
</div>
</div>
</body>
</html>
"""
return self.send_email(user_email, subject, body)
# 全局邮件服务实例
email_service = EmailService()

196
models.py
View File

@@ -41,8 +41,25 @@ class User(db.Model):
# 余额
balance = db.Column(db.Float, default=0.0) # 账户余额(元)
# 手机号
phone = db.Column(db.String(20), nullable=True) # 手机号
phone_verified = db.Column(db.Boolean, default=False) # 手机号已验证
# 邀请系统
invite_code = db.Column(db.String(10), unique=True, nullable=True) # 用户专属邀请码
invited_by = db.Column(db.Integer, nullable=True) # 邀请人ID
invite_count = db.Column(db.Integer, default=0) # 已邀请人数
invite_rewards = db.Column(db.Float, default=0.0) # 邀请奖励金额
# 邮件通知设置
email_notify = db.Column(db.Boolean, default=True) # 邮件通知开关
notify_on_complete = db.Column(db.Boolean, default=True) # 翻译完成通知
notify_on_expire = db.Column(db.Boolean, default=True) # 会员到期提醒
# 关系
translations = db.relationship('Translation', backref='user', lazy=True)
invitations = db.relationship('UserInvitation', backref='inviter', lazy=True,
foreign_keys='UserInvitation.inviter_id')
recharges = db.relationship('UserRecharge', backref='user', lazy=True)
refunds = db.relationship('UserRefund', backref='user', lazy=True)
@@ -103,11 +120,19 @@ class User(db.Model):
'id': self.id,
'username': self.username,
'email': self.email,
'phone': self.phone,
'phone_verified': self.phone_verified,
'user_type': self.user_type,
'is_vip': self.is_vip(),
'is_admin': self.is_admin,
'is_active': self.is_active,
'balance': self.balance,
'invite_code': self.invite_code,
'invite_count': self.invite_count,
'invite_rewards': self.invite_rewards,
'email_notify': self.email_notify,
'notify_on_complete': self.notify_on_complete,
'notify_on_expire': self.notify_on_expire,
'daily_count': self.daily_count,
'total_count': self.total_count,
'created_at': self.created_at.isoformat() if self.created_at else None,
@@ -710,3 +735,174 @@ class AccountTransaction(db.Model):
'description': self.description,
'created_at': self.created_at.isoformat() if self.created_at else None,
}
# ==================== 用户邀请记录 ====================
class UserInvitation(db.Model):
"""用户邀请记录"""
__tablename__ = 'user_invitations'
id = db.Column(db.Integer, primary_key=True)
# 邀请人
inviter_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
invite_code = db.Column(db.String(10), nullable=False) # 使用的邀请码
# 被邀请人
invitee_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=True) # 注册后关联
invitee_email = db.Column(db.String(120), nullable=True) # 被邀请人邮箱(注册前)
# 奖励
reward_amount = db.Column(db.Float, default=0.0) # 奖励金额
reward_given = db.Column(db.Boolean, default=False) # 是否已发放奖励
# 状态
status = db.Column(db.String(20), default='pending') # pending, registered, rewarded
# 时间
created_at = db.Column(db.DateTime, default=datetime.utcnow)
registered_at = db.Column(db.DateTime, nullable=True) # 被邀请人注册时间
rewarded_at = db.Column(db.DateTime, nullable=True) # 奖励发放时间
# 关系
invitee = db.relationship('User', backref=db.backref('invited_by_record', lazy=True),
foreign_keys=[invitee_id])
def to_dict(self):
return {
'id': self.id,
'inviter_id': self.inviter_id,
'invite_code': self.invite_code,
'invitee_id': self.invitee_id,
'invitee_email': self.invitee_email,
'reward_amount': self.reward_amount,
'reward_given': self.reward_given,
'status': self.status,
'created_at': self.created_at.isoformat() if self.created_at else None,
}
# ==================== 邀请奖励配置 ====================
class InviteRewardConfig(db.Model):
"""邀请奖励配置"""
__tablename__ = 'invite_reward_config'
id = db.Column(db.Integer, primary_key=True)
# 奖励规则
reward_amount = db.Column(db.Float, default=5.0) # 邀请奖励金额(元)
invitee_bonus = db.Column(db.Float, default=2.0) # 被邀请人 bonus
min_invitee_user_type = db.Column(db.String(20), default='free') # 被邀请人最低等级才能奖励
# 限制
max_daily_invites = db.Column(db.Integer, default=10) # 每日最大邀请次数
max_total_invites = db.Column(db.Integer, default=100) # 总最大邀请次数
# 状态
is_active = db.Column(db.Boolean, default=True)
# 时间
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,
'reward_amount': self.reward_amount,
'invitee_bonus': self.invitee_bonus,
'min_invitee_user_type': self.min_invitee_user_type,
'max_daily_invites': self.max_daily_invites,
'max_total_invites': self.max_total_invites,
'is_active': self.is_active,
}
# ==================== 邮件通知记录 ====================
class EmailNotification(db.Model):
"""邮件通知记录"""
__tablename__ = 'email_notifications'
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
# 邮件信息
email_to = db.Column(db.String(120), nullable=False) # 收件人
email_type = db.Column(db.String(30), nullable=False) # complete, expire, invoice, welcome
# 附件
has_attachment = db.Column(db.Boolean, default=False)
attachment_path = db.Column(db.String(255), nullable=True) # 附件文件路径
attachment_name = db.Column(db.String(100), nullable=True) # 附件显示名称
# 内容
subject = db.Column(db.String(200), nullable=True)
body_preview = db.Column(db.Text, nullable=True) # 正文预览
# 状态
status = db.Column(db.String(20), default='pending') # pending, sent, failed
error_message = db.Column(db.String(255), nullable=True)
# 关联
related_id = db.Column(db.Integer, nullable=True) # 关联ID翻译ID、订单ID等
related_type = db.Column(db.String(30), nullable=True) # 关联类型
# 时间
created_at = db.Column(db.DateTime, default=datetime.utcnow)
sent_at = db.Column(db.DateTime, nullable=True)
# 关系
user = db.relationship('User', backref=db.backref('email_notifications', lazy=True))
def to_dict(self):
return {
'id': self.id,
'user_id': self.user_id,
'email_to': self.email_to,
'email_type': self.email_type,
'has_attachment': self.has_attachment,
'subject': self.subject,
'status': self.status,
'created_at': self.created_at.isoformat() if self.created_at else None,
'sent_at': self.sent_at.isoformat() if self.sent_at else None,
}
# ==================== 邮件模板配置 ====================
class EmailTemplateConfig(db.Model):
"""邮件模板配置"""
__tablename__ = 'email_template_config'
id = db.Column(db.Integer, primary_key=True)
template_key = db.Column(db.String(50), unique=True, nullable=False) # complete, expire, invoice, welcome, invite
template_name = db.Column(db.String(100), nullable=False) # 显示名称
# 模板内容
subject_template = db.Column(db.String(200), nullable=False) # 主题模板
body_template = db.Column(db.Text, nullable=False) # 正文模板(支持变量)
# 支持变量说明
variables = db.Column(db.Text, nullable=True) # JSON: ["username", "filename", ...]
# 状态
is_active = db.Column(db.Boolean, default=True)
# 时间
created_at = db.Column(db.DateTime, default=datetime.utcnow)
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
def get_variables(self):
import json
try:
return json.loads(self.variables) if self.variables else []
except:
return []
def to_dict(self):
return {
'id': self.id,
'template_key': self.template_key,
'template_name': self.template_name,
'subject_template': self.subject_template,
'is_active': self.is_active,
}

View File

@@ -134,7 +134,7 @@
</div>
<!-- 会员购买记录 -->
<div class="card">
<div class="card mb-4">
<div class="card-header">
<h5 class="mb-0">🎫 会员购买记录</h5>
</div>
@@ -157,6 +157,111 @@
</div>
</div>
</div>
<!-- 账户设置 -->
<div class="card mb-4">
<div class="card-header">
<h5 class="mb-0">⚙️ 账户设置</h5>
</div>
<div class="card-body">
<div class="row">
<!-- 手机号绑定 -->
<div class="col-md-6 mb-3">
<div class="border rounded p-3">
<h6 class="mb-3">📱 手机号</h6>
<div id="phoneStatus">
{% if user.phone %}
<p class="text-success">已绑定:{{ user.phone }}
{% if user.phone_verified %}<span class="badge bg-success">已验证</span>{% endif %}
</p>
<button class="btn btn-outline-secondary btn-sm" onclick="showPhoneModal()">更换手机号</button>
{% else %}
<p class="text-muted">未绑定手机号</p>
<button class="btn btn-primary btn-sm" onclick="showPhoneModal()">绑定手机号</button>
{% endif %}
</div>
</div>
</div>
<!-- 邮件通知设置 -->
<div class="col-md-6 mb-3">
<div class="border rounded p-3">
<h6 class="mb-3">📧 邮件通知</h6>
<div class="form-check mb-2">
<input class="form-check-input" type="checkbox" id="notifyComplete"
{% if user.notify_on_complete %}checked{% endif %}
onchange="updateNotifySettings()">
<label class="form-check-label">翻译完成通知(含附件)</label>
</div>
<div class="form-check mb-2">
<input class="form-check-input" type="checkbox" id="notifyExpire"
{% if user.notify_on_expire %}checked{% endif %}
onchange="updateNotifySettings()">
<label class="form-check-label">会员到期提醒</label>
</div>
<small class="text-muted">通知发送至:{{ user.email }}</small>
</div>
</div>
</div>
</div>
</div>
<!-- 邀请好友 -->
<div class="card mb-4">
<div class="card-header">
<h5 class="mb-0">🎁 邀请好友</h5>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<div class="border rounded p-3 bg-light">
<h6>您的专属邀请码</h6>
<div class="input-group mb-3">
<input type="text" class="form-control form-control-lg text-center fw-bold"
id="inviteCode" value="{{ user.invite_code or '生成中...' }}" readonly>
<button class="btn btn-outline-primary" onclick="copyInviteCode()">复制</button>
</div>
<p class="text-muted mb-0">
分享邀请码给好友,好友注册后您可获得 <strong class="text-success">¥5</strong> 奖励!
</p>
</div>
</div>
<div class="col-md-6">
<div class="border rounded p-3">
<h6>邀请统计</h6>
<div class="d-flex justify-content-around text-center">
<div>
<div class="fs-4 fw-bold text-primary">{{ user.invite_count }}</div>
<small class="text-muted">已邀请</small>
</div>
<div>
<div class="fs-4 fw-bold text-success">¥{{ "%.2f"|format(user.invite_rewards) }}</div>
<small class="text-muted">累计奖励</small>
</div>
</div>
</div>
</div>
</div>
<!-- 邀请记录 -->
<div class="mt-3">
<h6>邀请记录</h6>
<table class="table table-sm">
<thead>
<tr>
<th>被邀请人</th>
<th>注册时间</th>
<th>奖励</th>
<th>状态</th>
</tr>
</thead>
<tbody id="inviteRecords">
<!-- 动态加载 -->
</tbody>
</table>
</div>
</div>
</div>
</main>
<!-- 充值模态框 -->
@@ -340,10 +445,88 @@
// 初始化
loadTransactions();
loadPurchases();
loadInviteRecords();
document.getElementById('transactionFilter').addEventListener('change', function() {
loadTransactions(this.value);
});
// 加载邀请记录
function loadInviteRecords() {
fetch('/api/profile/invitations')
.then(r => r.json())
.then(data => {
const tbody = document.getElementById('inviteRecords');
if (data.invitations && data.invitations.length > 0) {
tbody.innerHTML = data.invitations.map(inv => {
const statusMap = { 'pending': '待注册', 'registered': '已注册', 'rewarded': '已奖励' };
const statusClass = { 'pending': 'text-muted', 'registered': 'text-info', 'rewarded': 'text-success' };
return `<tr>
<td>${inv.invitee_email || '用户' + inv.invitee_id}</td>
<td>${inv.created_at}</td>
<td>¥${inv.reward_amount.toFixed(2)}</td>
<td><span class="${statusClass[inv.status]}">${statusMap[inv.status]}</span></td>
</tr>`;
}).join('');
} else {
tbody.innerHTML = '<tr><td colspan="4" class="text-muted text-center">暂无邀请记录</td></tr>';
}
});
}
// 复制邀请码
function copyInviteCode() {
const code = document.getElementById('inviteCode').value;
navigator.clipboard.writeText(code).then(() => {
alert('邀请码已复制:' + code);
});
}
// 更新通知设置
function updateNotifySettings() {
const notifyComplete = document.getElementById('notifyComplete').checked;
const notifyExpire = document.getElementById('notifyExpire').checked;
fetch('/api/profile/settings', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
notify_on_complete: notifyComplete,
notify_on_expire: notifyExpire
})
})
.then(r => r.json())
.then(data => {
if (data.success) {
// 不刷新页面,只显示提示
console.log('设置已更新');
}
});
}
// 绑定手机号
function showPhoneModal() {
// 简单处理,直接弹输入框
const phone = prompt('请输入手机号:');
if (phone && phone.length >= 10) {
fetch('/api/profile/settings', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ phone: phone })
})
.then(r => r.json())
.then(data => {
if (data.success) {
alert('手机号已绑定');
location.reload();
} else {
alert('绑定失败:' + data.error);
}
});
} else if (phone) {
alert('手机号格式不正确');
}
}
</script>
</body>
</html>