// 全局变量
let currentPage = 'dashboard';
// 页面加载时初始化
document.addEventListener('DOMContentLoaded', async () => {
await checkAuth();
await loadStats();
await loadApiKeys();
await loadRecentActivity();
});
// 检查认证状态
async function checkAuth() {
try {
const response = await fetch('/admin/auth/check');
if (!response.ok) {
window.location.href = '/admin/login.html';
}
} catch (error) {
console.error('认证检查失败:', error);
window.location.href = '/admin/login.html';
}
}
// 加载统计数据
async function loadStats() {
try {
const response = await fetch('/admin/stats');
const data = await response.json();
document.getElementById('apiKeysCount').textContent = data.apiKeys || 0;
document.getElementById('tokensCount').textContent = data.tokens || 0;
document.getElementById('todayRequests').textContent = data.todayRequests || 0;
document.getElementById('successRate').textContent = (data.successRate || 100) + '%';
} catch (error) {
console.error('加载统计数据失败:', error);
}
}
// 加载最近活动记录
async function loadRecentActivity() {
try {
const response = await fetch('/admin/stats/recent-activity?limit=10');
const activities = await response.json();
const container = document.getElementById('recentActivity');
if (activities.length === 0) {
container.innerHTML = '
暂无活动记录
';
return;
}
container.innerHTML = activities.map(activity => {
const timeAgo = getTimeAgo(activity.time);
return `
${escapeHtml(activity.title)}
${escapeHtml(activity.description)}
${timeAgo}
`;
}).join('');
} catch (error) {
console.error('加载最近活动失败:', error);
}
}
// 计算时间差
function getTimeAgo(timestamp) {
if (!timestamp) return '未知';
const now = new Date();
const time = new Date(timestamp);
const diff = Math.floor((now - time) / 1000); // 秒
if (diff < 60) return '刚刚';
if (diff < 3600) return `${Math.floor(diff / 60)} 分钟前`;
if (diff < 86400) return `${Math.floor(diff / 3600)} 小时前`;
if (diff < 604800) return `${Math.floor(diff / 86400)} 天前`;
return time.toLocaleDateString('zh-CN');
}
// 切换页面
function switchPage(event, page) {
event.preventDefault();
currentPage = page;
// 更新导航样式
document.querySelectorAll('.nav-item').forEach(item => {
item.classList.remove('active', 'text-white');
item.classList.add('text-gray-700');
});
event.currentTarget.classList.add('active');
event.currentTarget.classList.remove('text-gray-700');
// 隐藏所有页面
document.getElementById('dashboardPage').classList.add('hidden');
document.getElementById('apikeysPage').classList.add('hidden');
document.getElementById('accountsPage').classList.add('hidden');
document.getElementById('analyticsPage').classList.add('hidden');
document.getElementById('settingsPage').classList.add('hidden');
// 更新页面标题
const titles = {
dashboard: { title: '仪表盘', desc: '系统概览和实时数据' },
apikeys: { title: 'API Keys', desc: 'API 密钥管理' },
accounts: { title: '账号管理', desc: 'Tokens 账户管理' },
analytics: { title: '数据分析', desc: 'API 请求统计和分析' },
settings: { title: '系统设置', desc: '系统配置和偏好设置' }
};
document.getElementById('pageTitle').textContent = titles[page].title;
document.getElementById('pageDesc').textContent = titles[page].desc;
// 显示对应页面
if (page === 'dashboard') {
document.getElementById('dashboardPage').classList.remove('hidden');
} else if (page === 'apikeys') {
document.getElementById('apikeysPage').classList.remove('hidden');
loadApiKeys();
} else if (page === 'accounts') {
document.getElementById('accountsPage').classList.remove('hidden');
loadTokens();
loadLoadBalanceStrategy();
} else if (page === 'analytics') {
document.getElementById('analyticsPage').classList.remove('hidden');
loadAnalytics();
} else if (page === 'settings') {
document.getElementById('settingsPage').classList.remove('hidden');
}
}
// 切换账号管理标签
// 已移除,不再需要
// ==================== API Keys 管理 ====================
async function loadApiKeys() {
try {
const response = await fetch('/admin/api-keys');
const data = await response.json();
const tbody = document.getElementById('apiKeysTable');
if (data.length === 0) {
tbody.innerHTML = '| 暂无 API Key |
';
return;
}
tbody.innerHTML = data.map(key => `
| ${escapeHtml(key.name || '-')} |
${escapeHtml(key.key.substring(0, 20))}...
|
${key.usage_count || 0} |
${key.last_used_at ? new Date(key.last_used_at).toLocaleString('zh-CN') : '-'} |
${key.is_active ? '启用' : '禁用'}
|
|
`).join('');
} catch (error) {
console.error('加载 API Keys 失败:', error);
}
}
function showCreateApiKeyModal() {
document.getElementById('createApiKeyModal').classList.remove('hidden');
}
async function handleCreateApiKey(event) {
event.preventDefault();
const name = document.getElementById('apiKeyName').value;
try {
const response = await fetch('/admin/api-keys', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name })
});
const data = await response.json();
if (response.ok) {
document.getElementById('createApiKeyModal').classList.add('hidden');
document.getElementById('apiKeyName').value = '';
alert('API Key 创建成功!\n\n' + data.key + '\n\n请妥善保存,此 Key 不会再次显示!');
await loadApiKeys();
await loadStats();
} else {
alert('创建失败: ' + (data.error || '未知错误'));
}
} catch (error) {
alert('创建失败: ' + error.message);
}
}
async function toggleApiKey(id, currentStatus) {
try {
const response = await fetch(`/admin/api-keys/${id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ is_active: !currentStatus })
});
if (response.ok) {
await loadApiKeys();
await loadStats();
}
} catch (error) {
alert('操作失败: ' + error.message);
}
}
async function deleteApiKey(id) {
if (!confirm('确定要删除此 API Key 吗?')) return;
try {
const response = await fetch(`/admin/api-keys/${id}`, { method: 'DELETE' });
if (response.ok) {
await loadApiKeys();
await loadStats();
}
} catch (error) {
alert('删除失败: ' + error.message);
}
}
// ==================== Tokens 管理 ====================
let currentTokenPage = 1;
let tokenPageSize = 20;
let totalTokens = 0;
let selectedTokens = new Set();
async function loadTokens(page = 1) {
try {
currentTokenPage = page;
selectedTokens.clear();
updateBatchDeleteButton();
const response = await fetch(`/admin/tokens?page=${page}&limit=${tokenPageSize}`);
const result = await response.json();
const data = result.data || [];
const pagination = result.pagination || {};
totalTokens = pagination.total || 0;
// 更新账号总数显示
const totalCountEl = document.getElementById('totalTokensCount');
if (totalCountEl) {
totalCountEl.textContent = totalTokens;
}
const tbody = document.getElementById('tokensTable');
if (data.length === 0) {
tbody.innerHTML = '| 暂无 Token |
';
updateTokenPagination(0, 0);
return;
}
tbody.innerHTML = data.map(token => {
// 计算额度百分比
const quotaTotal = token.quota_total || 0;
const quotaUsed = token.quota_used || 0;
const quotaRemaining = token.quota_remaining || 0;
const quotaPercent = quotaTotal > 0 ? Math.round((quotaUsed / quotaTotal) * 100) : 0;
// 额度显示颜色
let quotaColor = 'text-green-600';
if (quotaPercent > 80) quotaColor = 'text-red-600';
else if (quotaPercent > 50) quotaColor = 'text-yellow-600';
// 额度显示文本
let quotaText = '-';
if (quotaTotal > 0) {
quotaText = `
${quotaRemaining.toLocaleString()} / ${quotaTotal.toLocaleString()}
${quotaPercent}% 已用
`;
}
return `
|
|
${escapeHtml(token.name || '-')} |
${quotaText} |
${token.total_requests || 0} |
${token.success_requests || 0} |
${token.failed_requests || 0} |
${token.expired_at ? new Date(token.expired_at).toLocaleString('zh-CN') : '-'} |
${token.is_active ? '启用' : '禁用'}
|
|
`;
}).join('');
updateTokenPagination(pagination.page, pagination.totalPages);
} catch (error) {
console.error('加载 Tokens 失败:', error);
}
}
function updateTokenPagination(currentPage, totalPages) {
const paginationEl = document.getElementById('tokenPagination');
if (!paginationEl) return;
if (totalPages <= 1) {
paginationEl.innerHTML = '';
return;
}
let html = '';
html += `
共 ${totalTokens} 个账号,第 ${currentPage}/${totalPages} 页
`;
html += '
';
// 上一页
if (currentPage > 1) {
html += ``;
} else {
html += ``;
}
// 页码
const maxPages = 5;
let startPage = Math.max(1, currentPage - Math.floor(maxPages / 2));
let endPage = Math.min(totalPages, startPage + maxPages - 1);
if (endPage - startPage < maxPages - 1) {
startPage = Math.max(1, endPage - maxPages + 1);
}
if (startPage > 1) {
html += ``;
if (startPage > 2) {
html += `...`;
}
}
for (let i = startPage; i <= endPage; i++) {
if (i === currentPage) {
html += ``;
} else {
html += ``;
}
}
if (endPage < totalPages) {
if (endPage < totalPages - 1) {
html += `...`;
}
html += ``;
}
// 下一页
if (currentPage < totalPages) {
html += ``;
} else {
html += ``;
}
html += '
';
paginationEl.innerHTML = html;
}
function showCreateTokenModal() {
document.getElementById('createTokenModal').classList.remove('hidden');
}
function showImportTokenModal() {
document.getElementById('importTokenModal').classList.remove('hidden');
// 监听文件选择
document.getElementById('tokenFileInput').addEventListener('change', handleFileSelect);
}
function closeImportModal() {
document.getElementById('importTokenModal').classList.add('hidden');
document.getElementById('tokenFileInput').value = '';
document.getElementById('tokenJsonContent').value = '';
document.getElementById('importPreview').classList.add('hidden');
}
function handleFileSelect(event) {
const files = event.target.files;
if (!files || files.length === 0) return;
// 如果只有一个文件,直接读取
if (files.length === 1) {
const reader = new FileReader();
reader.onload = function(e) {
document.getElementById('tokenJsonContent').value = e.target.result;
};
reader.onerror = function(e) {
alert('文件读取失败: ' + e.target.error);
};
reader.readAsText(files[0]);
return;
}
// 多个文件,合并成数组
let allTokens = [];
let filesRead = 0;
const totalFiles = files.length;
console.log(`开始读取 ${totalFiles} 个文件...`);
Array.from(files).forEach((file, index) => {
const reader = new FileReader();
reader.onload = function(e) {
try {
console.log(`读取文件 ${index + 1}/${totalFiles}: ${file.name}`);
const data = JSON.parse(e.target.result);
// 如果是数组,展开;如果是对象,作为单个元素
if (Array.isArray(data)) {
allTokens = allTokens.concat(data);
console.log(`文件 ${file.name} 包含 ${data.length} 个 token`);
} else {
allTokens.push(data);
console.log(`文件 ${file.name} 包含 1 个 token`);
}
} catch (error) {
console.error(`文件 ${file.name} 解析失败:`, error);
alert(`文件 ${file.name} 解析失败: ${error.message}`);
}
filesRead++;
// 所有文件都读取完成后,更新文本框
if (filesRead === totalFiles) {
console.log(`所有文件读取完成,共 ${allTokens.length} 个 token`);
document.getElementById('tokenJsonContent').value = JSON.stringify(allTokens, null, 2);
}
};
reader.onerror = function(e) {
console.error(`文件 ${file.name} 读取失败:`, e.target.error);
alert(`文件 ${file.name} 读取失败`);
filesRead++;
if (filesRead === totalFiles && allTokens.length > 0) {
document.getElementById('tokenJsonContent').value = JSON.stringify(allTokens, null, 2);
}
};
reader.readAsText(file);
});
}
let importData = null;
function previewImport() {
const jsonContent = document.getElementById('tokenJsonContent').value.trim();
if (!jsonContent) {
alert('请先选择文件或粘贴 JSON 内容');
return;
}
try {
importData = JSON.parse(jsonContent);
if (!Array.isArray(importData)) {
importData = [importData];
}
// 验证数据格式
const validTokens = importData.filter(token => {
return token.access_token && token.refresh_token;
});
if (validTokens.length === 0) {
alert('JSON 格式错误:未找到有效的 token 数据\n\n每个 token 必须包含 access_token 和 refresh_token 字段');
return;
}
// 显示预览
document.getElementById('importCount').textContent = validTokens.length;
const listEl = document.getElementById('importList');
listEl.innerHTML = validTokens.map((token, index) => `
${index + 1}. ${escapeHtml(token.name || token.email || token.account_id || 'Token ' + (index + 1))}
`).join('');
document.getElementById('importPreview').classList.remove('hidden');
importData = validTokens;
} catch (error) {
alert('JSON 解析失败:' + error.message);
}
}
async function handleImportTokens() {
if (!importData || importData.length === 0) {
alert('请先预览导入数据');
return;
}
if (!confirm(`确定要导入 ${importData.length} 个账户吗?`)) {
return;
}
try {
const response = await fetch('/admin/tokens/import', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ tokens: importData })
});
const data = await response.json();
if (response.ok) {
alert(`导入成功!\n成功:${data.success || 0} 个\n失败:${data.failed || 0} 个`);
closeImportModal();
await loadTokens();
await loadStats();
} else {
alert('导入失败: ' + (data.error || '未知错误'));
}
} catch (error) {
alert('导入失败: ' + error.message);
}
}
async function handleCreateToken(event) {
event.preventDefault();
const name = document.getElementById('tokenName').value;
const access_token = document.getElementById('accessToken').value;
const refresh_token = document.getElementById('refreshToken').value;
try {
const response = await fetch('/admin/tokens', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name, access_token, refresh_token })
});
const data = await response.json();
if (response.ok) {
document.getElementById('createTokenModal').classList.add('hidden');
document.getElementById('tokenName').value = '';
document.getElementById('accessToken').value = '';
document.getElementById('refreshToken').value = '';
alert('Token 添加成功!');
await loadTokens();
await loadStats();
} else {
alert('添加失败: ' + (data.error || '未知错误'));
}
} catch (error) {
alert('添加失败: ' + error.message);
}
}
async function toggleToken(id, currentStatus) {
try {
const response = await fetch(`/admin/tokens/${id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ is_active: !currentStatus })
});
if (response.ok) {
await loadTokens();
await loadStats();
}
} catch (error) {
alert('操作失败: ' + error.message);
}
}
async function deleteToken(id) {
if (!confirm('确定要删除此 Token 吗?')) return;
try {
const response = await fetch(`/admin/tokens/${id}`, { method: 'DELETE' });
if (response.ok) {
await loadTokens(currentTokenPage);
await loadStats();
}
} catch (error) {
alert('删除失败: ' + error.message);
}
}
async function refreshTokenQuota(id) {
try {
const response = await fetch(`/admin/tokens/${id}/quota`, { method: 'POST' });
const data = await response.json();
if (response.ok) {
await loadTokens(currentTokenPage);
if (data.quota) {
alert(`额度已更新\n总额度: ${data.quota.total.toLocaleString()}\n已使用: ${data.quota.used.toLocaleString()}\n剩余: ${data.quota.remaining.toLocaleString()}`);
}
} else {
alert('刷新额度失败: ' + (data.error || '未知错误'));
}
} catch (error) {
alert('刷新额度失败: ' + error.message);
}
}
async function refreshAllQuotas() {
if (!confirm('确定要刷新所有账号的额度吗?这可能需要一些时间。')) {
return;
}
try {
const response = await fetch('/admin/tokens/quota/refresh-all', { method: 'POST' });
const data = await response.json();
if (response.ok) {
await loadTokens(currentTokenPage);
alert(`批量刷新完成\n成功: ${data.success || 0} 个\n失败: ${data.failed || 0} 个`);
} else {
alert('批量刷新失败: ' + (data.error || '未知错误'));
}
} catch (error) {
alert('批量刷新失败: ' + error.message);
}
}
// ==================== 批量删除功能 ====================
function toggleTokenSelection(id) {
if (selectedTokens.has(id)) {
selectedTokens.delete(id);
} else {
selectedTokens.add(id);
}
updateBatchDeleteButton();
updateSelectAllCheckbox();
}
function toggleSelectAll() {
const checkbox = document.getElementById('selectAllTokens');
const checkboxes = document.querySelectorAll('.token-checkbox');
if (checkbox.checked) {
checkboxes.forEach(cb => {
const id = parseInt(cb.value);
selectedTokens.add(id);
cb.checked = true;
});
} else {
selectedTokens.clear();
checkboxes.forEach(cb => {
cb.checked = false;
});
}
updateBatchDeleteButton();
}
function updateSelectAllCheckbox() {
const checkbox = document.getElementById('selectAllTokens');
const checkboxes = document.querySelectorAll('.token-checkbox');
if (checkboxes.length === 0) {
checkbox.checked = false;
return;
}
const allChecked = Array.from(checkboxes).every(cb => cb.checked);
checkbox.checked = allChecked;
}
function updateBatchDeleteButton() {
const btn = document.getElementById('batchDeleteBtn');
const countSpan = document.getElementById('selectedCount');
if (selectedTokens.size > 0) {
btn.classList.remove('hidden');
countSpan.textContent = selectedTokens.size;
} else {
btn.classList.add('hidden');
}
}
async function batchDeleteTokens() {
if (selectedTokens.size === 0) {
alert('请先选择要删除的账号');
return;
}
if (!confirm(`确定要删除选中的 ${selectedTokens.size} 个账号吗?此操作不可恢复!`)) {
return;
}
try {
const ids = Array.from(selectedTokens);
const response = await fetch('/admin/tokens/batch-delete', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ids })
});
const data = await response.json();
if (response.ok) {
alert(`批量删除完成\n成功: ${data.success || 0} 个\n失败: ${data.failed || 0} 个`);
selectedTokens.clear();
await loadTokens(currentTokenPage);
await loadStats();
} else {
alert('批量删除失败: ' + (data.error || '未知错误'));
}
} catch (error) {
alert('批量删除失败: ' + error.message);
}
}
// ==================== 日志管理 ====================
async function loadAnalytics() {
// 加载统计数据
await loadAnalyticsStats();
// 加载图表
await loadCharts();
// 加载模型统计
await loadModelStats();
// 加载日志
await loadLogs();
}
let currentTimeRange = '24h';
function changeTimeRange(range) {
currentTimeRange = range;
// 更新按钮样式
document.querySelectorAll('.time-range-btn').forEach(btn => {
btn.classList.remove('bg-blue-500', 'text-white');
btn.classList.add('text-gray-700', 'hover:bg-gray-100');
});
event.target.classList.add('bg-blue-500', 'text-white');
event.target.classList.remove('text-gray-700', 'hover:bg-gray-100');
// 重新加载数据
loadAnalytics();
}
async function loadAnalyticsStats() {
try {
const response = await fetch(`/admin/stats/analytics?range=${currentTimeRange}`);
const data = await response.json();
document.getElementById('totalRequests').textContent = data.totalRequests || 0;
document.getElementById('successRequests').textContent = data.successRequests || 0;
document.getElementById('failedRequests').textContent = data.failedRequests || 0;
document.getElementById('avgResponseTime').textContent = (data.avgResponseTime || 0) + 'ms';
} catch (error) {
console.error('加载统计数据失败:', error);
}
}
let requestTrendChart = null;
let modelDistributionChart = null;
async function loadCharts() {
try {
const response = await fetch(`/admin/stats/charts?range=${currentTimeRange}`);
const data = await response.json();
// 请求量趋势图
const trendCtx = document.getElementById('requestTrendChart').getContext('2d');
if (requestTrendChart) {
requestTrendChart.destroy();
}
requestTrendChart = new Chart(trendCtx, {
type: 'line',
data: {
labels: data.trendLabels || [],
datasets: [{
label: '请求数',
data: data.trendData || [],
borderColor: '#3b82f6',
backgroundColor: 'rgba(59, 130, 246, 0.1)',
tension: 0.4
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false
}
},
scales: {
y: {
beginAtZero: true
}
}
}
});
// 模型使用分布饼图
const distCtx = document.getElementById('modelDistributionChart').getContext('2d');
if (modelDistributionChart) {
modelDistributionChart.destroy();
}
modelDistributionChart = new Chart(distCtx, {
type: 'pie',
data: {
labels: data.modelLabels || [],
datasets: [{
data: data.modelData || [],
backgroundColor: [
'#3b82f6',
'#10b981',
'#f59e0b',
'#ef4444',
'#8b5cf6',
'#ec4899'
]
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'right'
}
}
}
});
} catch (error) {
console.error('加载图表失败:', error);
}
}
async function loadModelStats() {
try {
const response = await fetch(`/admin/stats/accounts?range=${currentTimeRange}`);
const data = await response.json();
const tbody = document.getElementById('accountStatsTable');
if (data.length === 0) {
tbody.innerHTML = '| 暂无数据 |
';
return;
}
tbody.innerHTML = data.map(account => `
| ${escapeHtml(account.name)} |
${account.requests} |
${account.successRate}%
|
${account.avgResponseTime}ms |
${account.lastUsed ? new Date(account.lastUsed).toLocaleString('zh-CN') : '-'} |
`).join('');
} catch (error) {
console.error('加载账号统计失败:', error);
}
}
async function loadLogs() {
try {
const response = await fetch(`/admin/stats/logs?limit=50&range=${currentTimeRange}`);
const data = await response.json();
const tbody = document.getElementById('logsTable');
if (data.length === 0) {
tbody.innerHTML = '| 暂无日志 |
';
return;
}
tbody.innerHTML = data.map(log => `
| ${new Date(log.created_at).toLocaleString('zh-CN')} |
${log.api_key_name || log.api_key_id || '-'} |
${escapeHtml(log.model || '-')} |
${log.status_code}
|
${log.response_time || '-'}ms |
`).join('');
} catch (error) {
console.error('加载日志失败:', error);
}
}
// ==================== 工具函数 ====================
async function handleLogout() {
if (!confirm('确定要退出登录吗?')) return;
try {
await fetch('/admin/auth/logout', { method: 'POST' });
window.location.href = '/admin/login.html';
} catch (error) {
window.location.href = '/admin/login.html';
}
}
function copyToClipboard(text) {
navigator.clipboard.writeText(text).then(() => {
alert('已复制到剪贴板!');
}).catch(() => {
alert('复制失败,请手动复制');
});
}
// ==================== 负载均衡策略管理 ====================
async function loadLoadBalanceStrategy() {
try {
const response = await fetch('/admin/settings/load-balance-strategy');
const data = await response.json();
const select = document.getElementById('loadBalanceStrategy');
if (select && data.strategy) {
select.value = data.strategy;
}
} catch (error) {
console.error('加载负载均衡策略失败:', error);
}
}
async function changeLoadBalanceStrategy() {
const select = document.getElementById('loadBalanceStrategy');
const strategy = select.value;
try {
const response = await fetch('/admin/settings/load-balance-strategy', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ strategy })
});
const data = await response.json();
if (response.ok) {
alert('负载均衡策略已更新为:' + (strategy === 'round-robin' ? '轮询' : strategy === 'random' ? '随机' : '最少使用'));
} else {
alert('更新失败: ' + (data.error || '未知错误'));
}
} catch (error) {
alert('更新失败: ' + error.message);
}
}
// ==================== 修改密码 ====================
function showChangePasswordModal() {
document.getElementById('changePasswordModal').classList.remove('hidden');
}
function closeChangePasswordModal() {
document.getElementById('changePasswordModal').classList.add('hidden');
document.getElementById('currentPassword').value = '';
document.getElementById('newPassword').value = '';
document.getElementById('confirmPassword').value = '';
}
async function handleChangePassword(event) {
event.preventDefault();
const currentPassword = document.getElementById('currentPassword').value;
const newPassword = document.getElementById('newPassword').value;
const confirmPassword = document.getElementById('confirmPassword').value;
if (newPassword !== confirmPassword) {
alert('两次输入的新密码不一致');
return;
}
if (newPassword.length < 6) {
alert('密码长度至少 6 位');
return;
}
try {
const response = await fetch('/admin/auth/change-password', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ oldPassword: currentPassword, newPassword })
});
const data = await response.json();
if (response.ok) {
alert('密码修改成功,请重新登录');
closeChangePasswordModal();
window.location.href = '/admin/login.html';
} else {
alert('修改失败: ' + (data.error || '未知错误'));
}
} catch (error) {
alert('修改失败: ' + error.message);
}
}
// ==================== 工具函数 ====================
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}