feat: 添加手机号、邀请好友、邮件通知功能
- 用户模型添加:手机号、邀请码、邀请统计、邮件通知设置 - 邀请好友系统:专属邀请码、邀请奖励(¥5/人) - 邮件通知:翻译完成通知(含附件)、欢迎邮件、到期提醒 - 新增模型:UserInvitation, InviteRewardConfig, EmailNotification, EmailTemplateConfig - 个人中心添加:手机号绑定、通知设置、邀请好友模块 - email_service.py:邮件发送服务(支持附件) 新用户注册奖励:¥2 邀请人奖励:¥5/人
This commit is contained in:
@@ -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>
|
||||
Reference in New Issue
Block a user