Files
jj2/admin.js
LL 0cfefbebd8 feat: 添加基金监控系统基础功能
添加基金监控系统相关文件,包括邮件发送功能、基金数据配置、测试脚本等。主要包含以下内容:

1. 添加PHPMailer库及相关语言文件
2. 添加基金配置数据文件(fund_config.json, fund_names.json等)
3. 添加邮件发送测试脚本(test_email.php, test_fund_email.php等)
4. 添加.gitignore文件忽略不必要的文件
5. 添加composer.json配置依赖

Signed-off-by: LL <LL>
2025-12-12 14:14:07 +08:00

1065 lines
35 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 全局变量
let fundsData = [];
let fundChart = null;
// 页面加载完成后初始化
document.addEventListener('DOMContentLoaded', function() {
loadFundsData();
loadOperationLog();
populateFundSelector();
loadEmailConfig();
// 表单提交事件
document.getElementById('fundForm').addEventListener('submit', function(e) {
e.preventDefault();
saveFund();
});
// SMTP配置表单提交事件
if (document.getElementById('smtpConfigForm')) {
console.log('找到smtpConfigForm添加提交事件监听');
document.getElementById('smtpConfigForm').addEventListener('submit', function(e) {
console.log('表单提交事件触发');
e.preventDefault();
console.log('阻止默认提交行为');
saveSmtpConfig();
});
} else {
console.log('未找到smtpConfigForm元素');
}
});
// 加载推荐基金数据
async function loadRecommendedFunds() {
try {
showLoading();
const response = await fetch('admin_api.php?action=get_recommended_funds');
const result = await response.json();
if (result.success) {
renderRecommendedFundsTable(result.data);
} else {
throw new Error(result.message || '加载数据失败');
}
} catch (error) {
console.error('加载推荐基金数据失败:', error);
showMessage('数据加载失败: ' + error.message, 'error');
} finally {
hideLoading();
}
}
// 渲染推荐基金表格
function renderRecommendedFundsTable(data) {
const tbody = document.getElementById('recommendedFundsList');
if (!data || data.length === 0) {
tbody.innerHTML = `
<tr>
<td colspan="7" style="text-align: center; padding: 40px; color: var(--gray);">
<i class="fas fa-inbox" style="font-size: 3rem; margin-bottom: 10px; display: block; opacity: 0.5;"></i>
暂无推荐基金数据
</td>
</tr>
`;
return;
}
let html = '';
data.forEach((fund, index) => {
// 处理状态显示,支持多种可能的状态值
const getStatusText = (status) => {
const normalizedStatus = (status || '').toLowerCase().trim();
if (normalizedStatus.includes('approve') || normalizedStatus === '已批准') {
return '已批准';
} else if (normalizedStatus.includes('pend') || normalizedStatus === '待审核') {
return '待审核';
} else if (normalizedStatus.includes('reject') || normalizedStatus === '已拒绝') {
return '已拒绝';
}
return '未知';
};
const getStatusClass = (status) => {
const normalizedStatus = (status || '').toLowerCase().trim();
if (normalizedStatus.includes('approve') || normalizedStatus === '已批准') {
return 'status-approved';
} else if (normalizedStatus.includes('pend') || normalizedStatus === '待审核') {
return 'status-pending';
} else if (normalizedStatus.includes('reject') || normalizedStatus === '已拒绝') {
return 'status-rejected';
}
return 'status-unknown';
};
const statusText = getStatusText(fund.status);
const statusClass = getStatusClass(fund.status);
// 根据状态决定操作按钮
let actionButtons = '';
if (fund.status === 'pending') {
actionButtons = `
<button class="btn btn-primary btn-sm" onclick="approveRecommendedFund('${fund.fund_code}')">
<i class="fas fa-check"></i> 批准
</button>
<button class="btn btn-danger btn-sm" onclick="deleteRecommendedFund('${fund.fund_code}')">
<i class="fas fa-trash"></i> 删除
</button>
`;
} else {
// 已批准或已拒绝状态只显示删除按钮
actionButtons = `
<button class="btn btn-danger btn-sm" onclick="deleteRecommendedFund('${fund.fund_code}')">
<i class="fas fa-trash"></i> 删除
</button>
`;
}
html += `
<tr class="fund-row">
<td class="fund-code-cell"><strong>${fund.fund_code}</strong></td>
<td class="fund-ip-cell">${fund.ip}</td>
<td class="fund-time-cell">${fund.timestamp}</td>
<td class="fund-channel-cell">${fund.channel}</td>
<td class="fund-amount-cell">${parseFloat(fund.amount).toFixed(2)}</td>
<td class="fund-status-cell">
<span class="status-badge ${statusClass}">
${statusText}
</span>
</td>
<td class="fund-actions-cell">
<div class="action-buttons">
${actionButtons}
</div>
</td>
</tr>
`;
});
tbody.innerHTML = html;
}
// 搜索推荐基金
function searchRecommendedFunds(keyword) {
const rows = document.querySelectorAll('#recommendedFundsList tr');
keyword = keyword.toLowerCase();
rows.forEach(row => {
const fundCode = row.querySelector('td:first-child').textContent.toLowerCase();
if (fundCode.includes(keyword)) {
row.style.display = '';
} else {
row.style.display = 'none';
}
});
}
// 批准推荐基金
async function approveRecommendedFund(fundCode) {
if (!confirm(`确定要批准基金 ${fundCode} 吗?`)) {
return;
}
try {
showLoading();
const response = await fetch('admin_api.php?action=approve_recommended_fund&fund_code=' + fundCode);
const result = await response.json();
if (result.success) {
showMessage('基金批准成功', 'success');
loadRecommendedFunds();
} else {
throw new Error(result.message || '操作失败');
}
} catch (error) {
console.error('批准推荐基金失败:', error);
showMessage('操作失败: ' + error.message, 'error');
} finally {
hideLoading();
}
}
// 删除推荐基金
async function deleteRecommendedFund(fundCode) {
if (!confirm(`确定要删除推荐基金 ${fundCode} 吗?此操作不可恢复。`)) {
return;
}
try {
showLoading();
const response = await fetch('admin_api.php?action=delete_recommended_fund&fund_code=' + fundCode);
const result = await response.json();
if (result.success) {
showMessage('基金删除成功', 'success');
loadRecommendedFunds();
} else {
throw new Error(result.message || '操作失败');
}
} catch (error) {
console.error('删除推荐基金失败:', error);
showMessage('操作失败: ' + error.message, 'error');
} finally {
hideLoading();
}
}
// 切换标签页
function switchTab(tabName) {
// 隐藏所有标签内容
document.querySelectorAll('.tab-content').forEach(tab => {
tab.classList.remove('active');
});
// 移除所有标签的激活状态
document.querySelectorAll('.tab').forEach(tab => {
tab.classList.remove('active');
});
// 显示选中的标签内容
document.getElementById(tabName).classList.add('active');
// 激活选中的标签
if (event) {
event.target.classList.add('active');
} else {
// 如果event对象不存在通过其他方式获取激活的标签
document.querySelectorAll('.tab').forEach(tab => {
if (tab.getAttribute('onclick') && tab.getAttribute('onclick').includes(tabName)) {
tab.classList.add('active');
}
});
}
// 如果是图表标签,加载默认图表
if (tabName === 'fundCharts') {
const fundSelector = document.getElementById('fundSelector');
if (fundSelector.value) {
loadFundChart(fundSelector.value);
}
} else if (tabName === 'recommendedFunds') {
// 加载推荐基金数据
loadRecommendedFunds();
// 添加搜索事件监听
const searchInput = document.getElementById('searchRecommendedFund');
searchInput.oninput = function() {
searchRecommendedFunds(this.value);
};
} else if (tabName === 'emailSettings') {
// 加载邮箱配置
loadEmailConfig();
}
}
// 加载基金数据
async function loadFundsData() {
try {
showLoading();
const response = await fetch('admin_api.php?action=get_funds');
const result = await response.json();
if (result.success) {
fundsData = result.data;
renderFundsTable();
populateFundSelector();
} else {
throw new Error(result.message || '加载数据失败');
}
} catch (error) {
console.error('加载基金数据失败:', error);
showMessage('数据加载失败: ' + error.message, 'error');
}
}
// 渲染基金表格
function renderFundsTable() {
const tbody = document.getElementById('fundsTableBody');
if (fundsData.length === 0) {
tbody.innerHTML = `
<tr>
<td colspan="5" style="text-align: center; padding: 40px; color: var(--gray);">
<i class="fas fa-inbox" style="font-size: 3rem; margin-bottom: 10px; display: block; opacity: 0.5;"></i>
暂无基金数据,点击"添加基金"开始配置
</td>
</tr>
`;
return;
}
let html = '';
fundsData.forEach((fund, index) => {
const channelName = getChannelName(fund.channel);
const channelClass = getChannelClass(fund.channel);
html += `
<tr>
<td><strong>${fund.fund_code}</strong></td>
<td>${fund.name || '加载中...'}</td>
<td>
<span class="channel-badge ${channelClass}">
${getChannelIcon(fund.channel)} ${channelName}
</span>
</td>
<td>${parseFloat(fund.investment).toFixed(2)}</td>
<td>
<div class="action-buttons">
<button class="btn btn-outline btn-sm" onclick="editFund(${index})">
<i class="fas fa-edit"></i> 编辑
</button>
<button class="btn btn-danger btn-sm" onclick="deleteFund(${index})">
<i class="fas fa-trash"></i> 删除
</button>
</div>
</td>
</tr>
`;
});
tbody.innerHTML = html;
// 加载基金名称
loadFundNames();
}
// 加载基金名称
async function loadFundNames() {
const nameCells = document.querySelectorAll('#fundsTableBody td:nth-child(2)');
// 先尝试从localStorage获取缓存的基金名称
const fundNameCache = JSON.parse(localStorage.getItem('fundNameCache') || '{}');
// 收集所有需要获取名称的基金代码
const fundsWithoutNames = fundsData.filter(fund => !fund.name && !fundNameCache[fund.fund_code]);
// 先应用缓存的名称
fundsData.forEach((fund, index) => {
if (fundNameCache[fund.fund_code]) {
nameCells[index].textContent = fundNameCache[fund.fund_code];
fundsData[index].name = fundNameCache[fund.fund_code];
}
});
// 如果有需要获取名称的基金,通过服务器端代理获取
if (fundsWithoutNames.length > 0) {
try {
const fundCodes = fundsWithoutNames.map(fund => fund.fund_code).join(',');
const response = await fetch(`admin_api.php?action=get_fund_names&fund_codes=${fundCodes}`);
const result = await response.json();
if (result.success && result.data) {
// 更新页面显示和缓存
fundsData.forEach((fund, index) => {
if (result.data[fund.fund_code]) {
const fundName = result.data[fund.fund_code];
nameCells[index].textContent = fundName;
fundsData[index].name = fundName;
fundNameCache[fund.fund_code] = fundName;
}
});
// 保存缓存
localStorage.setItem('fundNameCache', JSON.stringify(fundNameCache));
}
} catch (error) {
console.error('获取基金名称失败:', error);
// 即使失败也给每个基金一个默认名称
fundsData.forEach((fund, index) => {
if (!fund.name && !fundNameCache[fund.fund_code]) {
nameCells[index].textContent = `基金${fund.fund_code}`;
}
});
}
}
}
// 填充基金选择器
function populateFundSelector() {
const selector = document.getElementById('fundSelector');
selector.innerHTML = '<option value="">请选择基金</option>';
fundsData.forEach(fund => {
const option = document.createElement('option');
option.value = fund.fund_code;
option.textContent = `${fund.fund_code} - ${fund.name || '加载中...'}`;
selector.appendChild(option);
});
}
// 加载操作日志
async function loadOperationLog() {
try {
const response = await fetch('admin_api.php?action=get_operation_log&limit=50');
const result = await response.json();
if (result.success) {
renderOperationLog(result.data);
} else {
throw new Error(result.message || '加载操作日志失败');
}
} catch (error) {
console.error('加载操作日志失败:', error);
document.getElementById('operationLogContent').innerHTML = `
<div style="text-align: center; padding: 40px; color: var(--gray);">
<i class="fas fa-exclamation-triangle"></i> 加载操作日志失败: ${error.message}
</div>
`;
}
}
// 渲染操作日志
function renderOperationLog(operations) {
const container = document.getElementById('operationLogContent');
if (operations.length === 0) {
container.innerHTML = `
<div style="text-align: center; padding: 40px; color: var(--gray);">
<i class="fas fa-inbox"></i> 暂无操作记录
</div>
`;
return;
}
let html = '';
operations.forEach(operation => {
const typeClass = `type-${operation.type}`;
const typeText = {
'add': '添加',
'update': '更新',
'delete': '删除'
}[operation.type] || operation.type;
const channelName = getChannelName(operation.channel);
html += `
<div class="operation-item">
<div class="operation-details">
<span class="operation-type ${typeClass}">${typeText}</span>
<strong>${operation.fund_code}</strong>
${operation.channel ? ` - ${channelName}` : ''}
${operation.investment ? ` - ${operation.investment}` : ''}
${operation.details ? ` - ${operation.details}` : ''}
</div>
<div class="operation-time">${formatTime(operation.date)}</div>
</div>
`;
});
container.innerHTML = html;
}
// 加载基金图表
async function loadFundChart(fundCode) {
if (!fundCode) {
document.getElementById('chartTitle').textContent = '请选择基金查看净值变化';
if (fundChart) {
fundChart.destroy();
}
return;
}
try {
const response = await fetch(`api.php?action=get_fund_chart&fund_code=${fundCode}`);
const result = await response.json();
if (result.success) {
renderFundChart(fundCode, result.data);
} else {
throw new Error(result.message || '加载图表数据失败');
}
} catch (error) {
console.error('加载基金图表失败:', error);
document.getElementById('chartTitle').textContent = `加载图表失败: ${error.message}`;
}
}
// 渲染基金图表
function renderFundChart(fundCode, chartData) {
const ctx = document.getElementById('fundChart').getContext('2d');
const fundName = fundsData.find(f => f.fund_code === fundCode)?.name || fundCode;
document.getElementById('chartTitle').textContent = `${fundName} - 近5日净值变化`;
// 销毁现有图表
if (fundChart) {
fundChart.destroy();
}
// 创建新图表
fundChart = new Chart(ctx, {
type: 'line',
data: {
labels: chartData.labels,
datasets: [
{
label: '估算净值',
data: chartData.netValues,
borderColor: '#6366f1',
backgroundColor: 'rgba(99, 102, 241, 0.2)',
borderWidth: 3,
fill: true,
tension: 1, // 最大张力,使曲线更平滑
pointRadius: 5,
pointHoverRadius: 8,
pointBackgroundColor: '#ffffff',
pointBorderColor: '#6366f1',
pointBorderWidth: 2,
pointHoverBackgroundColor: '#6366f1',
pointHoverBorderColor: '#ffffff',
pointHoverBorderWidth: 2
},
{
label: '涨跌幅 (%)',
data: chartData.changes,
borderColor: '#10b981',
backgroundColor: 'rgba(16, 185, 129, 0.2)',
borderWidth: 3,
fill: false,
tension: 1, // 最大张力,使曲线更平滑
pointRadius: 5,
pointHoverRadius: 8,
pointBackgroundColor: '#ffffff',
pointBorderColor: '#10b981',
pointBorderWidth: 2,
pointHoverBackgroundColor: '#10b981',
pointHoverBorderColor: '#ffffff',
pointHoverBorderWidth: 2,
yAxisID: 'y1'
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
interaction: {
mode: 'index',
intersect: false,
axis: 'x'
},
animation: {
duration: 1500,
easing: 'easeInOutQuart'
},
scales: {
x: {
grid: {
display: false
},
ticks: {
font: {
size: 12
}
}
},
y: {
type: 'linear',
display: true,
position: 'left',
title: {
display: true,
text: '估算净值',
font: {
size: 14,
weight: 'bold'
}
},
grid: {
color: 'rgba(0, 0, 0, 0.05)'
},
ticks: {
font: {
size: 11
},
callback: function(value) {
return value.toFixed(4);
}
}
},
y1: {
type: 'linear',
display: true,
position: 'right',
title: {
display: true,
text: '涨跌幅 (%)',
font: {
size: 14,
weight: 'bold'
}
},
grid: {
drawOnChartArea: false,
},
ticks: {
font: {
size: 11
},
callback: function(value) {
return value > 0 ? '+' + value.toFixed(2) + '%' : value.toFixed(2) + '%';
}
}
}
},
plugins: {
legend: {
position: 'top',
labels: {
usePointStyle: true,
padding: 20,
font: {
size: 13
}
}
},
tooltip: {
backgroundColor: 'rgba(0, 0, 0, 0.8)',
padding: 12,
titleFont: {
size: 14,
weight: 'bold'
},
bodyFont: {
size: 13
},
borderColor: '#6366f1',
borderWidth: 1,
displayColors: true,
callbacks: {
title: function(tooltipItems) {
return tooltipItems[0].label;
},
label: function(context) {
let label = context.dataset.label || '';
if (label) {
label += ': ';
}
if (context.datasetIndex === 0) {
label += context.parsed.y.toFixed(4);
} else {
const value = context.parsed.y;
label += (value > 0 ? '+' : '') + value.toFixed(2) + '%';
}
return label;
},
afterBody: function(context) {
// 添加额外信息或空行以美化显示
return '';
}
}
}
}
}
});
}
// 显示添加模态框
function showAddModal() {
document.getElementById('modalTitle').textContent = '添加基金';
document.getElementById('fundForm').reset();
document.getElementById('editIndex').value = '';
document.getElementById('fundModal').style.display = 'flex';
}
// 编辑基金
function editFund(index) {
const fund = fundsData[index];
document.getElementById('modalTitle').textContent = '编辑基金';
document.getElementById('fundCode').value = fund.fund_code;
document.getElementById('channel').value = fund.channel;
document.getElementById('investment').value = fund.investment;
document.getElementById('editIndex').value = index;
document.getElementById('fundModal').style.display = 'flex';
}
// 关闭模态框
function closeModal() {
document.getElementById('fundModal').style.display = 'none';
}
// 保存基金
async function saveFund() {
const editIndex = document.getElementById('editIndex').value;
const fundCode = document.getElementById('fundCode').value.trim();
const channel = document.getElementById('channel').value;
const investment = parseFloat(document.getElementById('investment').value);
// 验证表单数据
if (!fundCode) {
showMessage('请输入基金代码', 'error');
return;
}
if (!/^\d{6}$/.test(fundCode)) {
showMessage('基金代码必须是6位数字', 'error');
return;
}
if (!channel) {
showMessage('请选择渠道', 'error');
return;
}
if (!investment || investment <= 0) {
showMessage('投资金额必须大于0', 'error');
return;
}
// 检查重复(添加时检查,编辑时不检查自身)
if (editIndex === '' || fundsData[editIndex].fund_code !== fundCode) {
const exists = fundsData.some((fund, index) =>
fund.fund_code === fundCode && fund.channel === channel &&
(editIndex === '' || index !== parseInt(editIndex))
);
if (exists) {
showMessage('该渠道下已存在相同的基金代码', 'error');
return;
}
}
try {
const action = editIndex === '' ? 'add_fund' : 'update_fund';
const payload = {
fund_code: fundCode,
channel: channel,
investment: investment
};
if (editIndex !== '') {
payload.index = parseInt(editIndex);
}
console.log('发送请求:', action, payload);
const response = await fetch('admin_api.php?action=' + action, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(payload)
});
const result = await response.json();
console.log('响应结果:', result);
if (result.success) {
showMessage(result.message, 'success');
closeModal();
loadFundsData();
loadOperationLog(); // 刷新操作日志
} else {
throw new Error(result.message || '保存失败');
}
} catch (error) {
console.error('保存基金失败:', error);
showMessage('保存失败: ' + error.message, 'error');
}
}
// 删除基金
async function deleteFund(index) {
if (!confirm('确定要删除这只基金吗?')) {
return;
}
try {
const response = await fetch('admin_api.php?action=delete_fund', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ index: index })
});
const result = await response.json();
if (result.success) {
showMessage(result.message, 'success');
loadFundsData();
loadOperationLog(); // 刷新操作日志
} else {
throw new Error(result.message || '删除失败');
}
} catch (error) {
console.error('删除基金失败:', error);
showMessage('删除失败: ' + error.message, 'error');
}
}
// 工具函数
function getChannelName(channel) {
const channels = {
'0': '招商银行',
'1': '天天基金',
'2': '支付宝'
};
return channels[channel] || '未知渠道';
}
function getChannelClass(channel) {
const classMap = {
'0': 'channel-cmb',
'1': 'channel-tt',
'2': 'channel-zfb'
};
return classMap[channel] || 'channel-cmb';
}
function getChannelIcon(channel) {
const iconMap = {
'0': '🏦',
'1': '📱',
'2': '💙'
};
return iconMap[channel] || '🏦';
}
// 显示消息
function showMessage(message, type) {
const messageEl = document.getElementById('message');
messageEl.textContent = message;
messageEl.className = `alert alert-${type === 'success' ? 'success' : 'error'}`;
messageEl.style.display = 'block';
// 自动隐藏成功消息,错误消息保持显示直到用户操作
if (type === 'success') {
setTimeout(() => {
messageEl.style.display = 'none';
}, 3000);
}
}
// 显示加载状态
function showLoading() {
const tbody = document.getElementById('fundsTableBody');
tbody.innerHTML = `
<tr>
<td colspan="5" style="text-align: center; padding: 40px; color: var(--gray);">
<div style="display: inline-block; width: 20px; height: 20px; border: 2px solid #f3f3f3; border-top: 2px solid var(--primary); border-radius: 50%; animation: spin 1s linear infinite;"></div>
加载中...
</td>
</tr>
`;
}
// 格式化时间
function formatTime(dateString) {
const date = new Date(dateString);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
const seconds = String(date.getSeconds()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}
// 邮箱设置相关函数
/**
* 加载邮箱配置
*/
async function loadEmailConfig() {
try {
showLoading();
const response = await fetch('admin_api.php?action=get_email_config');
const result = await response.json();
if (result.success) {
const config = result.data;
// 填充SMTP配置表单
document.getElementById('smtpServer').value = config.smtp_server || '';
document.getElementById('smtpPort').value = config.smtp_port || '';
document.getElementById('smtpSecure').value = config.smtp_secure || '';
document.getElementById('username').value = config.username || '';
document.getElementById('password').value = config.password || '';
document.getElementById('fromEmail').value = config.from_email || '';
document.getElementById('fromName').value = config.from_name || '';
// 渲染收件人列表
renderEmailRecipients(config.to_emails || []);
} else {
throw new Error(result.message || '加载邮箱配置失败');
}
} catch (error) {
console.error('加载邮箱配置失败:', error);
showMessage('加载邮箱配置失败: ' + error.message, 'error');
} finally {
hideLoading();
}
}
/**
* 保存SMTP配置
*/
async function saveSmtpConfig() {
try {
console.log('saveSmtpConfig函数被调用');
showLoading();
// 检查表单元素是否存在
console.log('smtpServer元素:', document.getElementById('smtpServer'));
console.log('fromName元素:', document.getElementById('fromName'));
const smtpConfig = {
smtp_server: document.getElementById('smtpServer').value,
smtp_port: document.getElementById('smtpPort').value,
smtp_secure: document.getElementById('smtpSecure').value,
username: document.getElementById('username').value,
password: document.getElementById('password').value,
from_email: document.getElementById('fromEmail').value,
from_name: document.getElementById('fromName').value
};
console.log('表单数据:', smtpConfig);
const response = await fetch('admin_api.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
action: 'update_email_config',
...smtpConfig
})
});
const result = await response.json();
if (result.success) {
showMessage('SMTP配置保存成功', 'success');
} else {
throw new Error(result.message || '保存SMTP配置失败');
}
} catch (error) {
console.error('保存SMTP配置失败:', error);
showMessage('保存SMTP配置失败: ' + error.message, 'error');
} finally {
hideLoading();
}
}
/**
* 添加邮箱收件人
*/
async function addEmailRecipient() {
try {
showLoading();
const newEmail = document.getElementById('newEmail').value;
if (!newEmail) {
showMessage('请输入邮箱地址', 'warning');
return;
}
const response = await fetch('admin_api.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
action: 'add_email_recipient',
email: newEmail
})
});
const result = await response.json();
if (result.success) {
showMessage('邮箱收件人添加成功', 'success');
document.getElementById('newEmail').value = '';
renderEmailRecipients(result.data || []);
} else {
throw new Error(result.message || '添加邮箱收件人失败');
}
} catch (error) {
console.error('添加邮箱收件人失败:', error);
showMessage('添加邮箱收件人失败: ' + error.message, 'error');
} finally {
hideLoading();
}
}
/**
* 删除邮箱收件人
*/
async function deleteEmailRecipient(email) {
try {
showLoading();
const response = await fetch('admin_api.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
action: 'delete_email_recipient',
email: email
})
});
const result = await response.json();
if (result.success) {
showMessage('邮箱收件人删除成功', 'success');
renderEmailRecipients(result.data || []);
} else {
throw new Error(result.message || '删除邮箱收件人失败');
}
} catch (error) {
console.error('删除邮箱收件人失败:', error);
showMessage('删除邮箱收件人失败: ' + error.message, 'error');
} finally {
hideLoading();
}
}
/**
* 渲染邮箱收件人列表
*/
function renderEmailRecipients(emails) {
const listContainer = document.getElementById('emailRecipientsList');
if (!listContainer) {
return;
}
if (!emails || emails.length === 0) {
listContainer.innerHTML = '<p class="text-gray">暂无收件人</p>';
return;
}
let html = '<div class="flex flex-wrap gap-2">';
emails.forEach(email => {
html += `
<div class="flex items-center gap-1 bg-gray-100 px-3 py-1 rounded-full">
<span>${email}</span>
<button class="btn btn-danger btn-sm" onclick="deleteEmailRecipient('${email}')">
<i class="fas fa-times"></i>
</button>
</div>
`;
});
html += '</div>';
listContainer.innerHTML = html;
}
// 点击模态框外部关闭模态框
window.onclick = function(event) {
const modal = document.getElementById('fundModal');
if (event.target === modal) {
closeModal();
}
}