添加基金监控系统相关文件,包括邮件发送功能、基金数据配置、测试脚本等。主要包含以下内容: 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>
1065 lines
35 KiB
JavaScript
1065 lines
35 KiB
JavaScript
// 全局变量
|
||
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();
|
||
}
|
||
} |