// ====================================================================
// 登录验证
// ====================================================================
(function () {
const ACCESS_KEY = 'oadmin123';
const overlay = document.getElementById('loginOverlay');
if (!overlay) return;
if (sessionStorage.getItem('authed') === '1') {
overlay.classList.add('hidden');
return;
}
const pwdInput = document.getElementById('loginPassword');
const loginBtn = document.getElementById('loginBtn');
const errEl = document.getElementById('loginError');
function doLogin() {
if (pwdInput.value === ACCESS_KEY) {
sessionStorage.setItem('authed', '1');
overlay.classList.add('hidden');
errEl.textContent = '';
} else {
errEl.textContent = '密码错误,请重试';
pwdInput.value = '';
pwdInput.focus();
}
}
loginBtn.addEventListener('click', doLogin);
pwdInput.addEventListener('keydown', e => { if (e.key === 'Enter') doLogin(); });
pwdInput.focus();
})();
// 账号管理页面JavaScript
class AdminManager {
constructor() {
this.isAuthenticated = false;
this.token = '';
// 分页与搜索状态
this.page = 1;
this.pageSize = 10;
this.total = 0;
this.query = '';
this.init();
}
init() {
this.bindEvents();
this.checkStoredAuth();
}
bindEvents() {
// 登录表单
document.getElementById('loginForm').addEventListener('submit', this.handleLogin.bind(this));
// 刷新账号列表
document.getElementById('refreshAccountsBtn').addEventListener('click', () => {
this.page = 1;
this.loadAccounts();
});
// 搜索
const searchInput = document.getElementById('accountSearchInput');
const searchBtn = document.getElementById('searchAccountsBtn');
if (searchBtn) {
searchBtn.addEventListener('click', () => {
this.query = searchInput.value.trim();
this.page = 1;
this.loadAccounts();
});
}
if (searchInput) {
searchInput.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
this.query = searchInput.value.trim();
this.page = 1;
this.loadAccounts();
}
});
}
// 分页
const prevBtn = document.getElementById('prevPageBtn');
const nextBtn = document.getElementById('nextPageBtn');
if (prevBtn) {
prevBtn.addEventListener('click', () => {
if (this.page > 1) {
this.page -= 1;
this.loadAccounts();
}
});
}
if (nextBtn) {
nextBtn.addEventListener('click', () => {
const maxPage = Math.max(1, Math.ceil(this.total / this.pageSize));
if (this.page < maxPage) {
this.page += 1;
this.loadAccounts();
}
});
}
// 导入相关(存在才绑定)
const executeImportBtn = document.getElementById('executeImportBtn');
if (executeImportBtn) {
executeImportBtn.addEventListener('click', this.executeImport.bind(this));
}
const clearImportBtn = document.getElementById('clearImportBtn');
if (clearImportBtn) {
clearImportBtn.addEventListener('click', this.clearImport.bind(this));
}
// 导出
document.getElementById('exportDataBtn').addEventListener('click', this.exportData.bind(this));
// 退出登录
document.getElementById('logoutBtn').addEventListener('click', this.logout.bind(this));
// 单页:不使用tabs,直接加载列表
setTimeout(() => this.loadAccounts(), 100);
// 打开导入对话框
const openImportBtn = document.getElementById('openImportModalBtn');
if (openImportBtn) {
openImportBtn.addEventListener('click', () => {
const modal = new bootstrap.Modal(document.getElementById('importModal'));
modal.show();
});
}
}
checkStoredAuth() {
// 检查是否有保存的认证信息(仅在当前会话有效)
const storedToken = sessionStorage.getItem('admin_token');
if (storedToken) {
this.token = storedToken;
this.showManagement();
}
}
async handleLogin(event) {
event.preventDefault();
const tokenInput = document.getElementById('tokenInput');
const enteredToken = tokenInput.value.trim();
if (!enteredToken) {
this.showError('请输入管理令牌');
return;
}
try {
// 验证令牌
const isValid = await this.verifyToken(enteredToken);
if (isValid) {
this.token = enteredToken;
this.isAuthenticated = true;
// 保存到会话存储
sessionStorage.setItem('admin_token', enteredToken);
this.showManagement();
this.showSuccess('登录成功');
} else {
this.showError('令牌验证失败,请检查输入的令牌是否正确');
}
} catch (error) {
console.error('登录失败:', error);
this.showError('登录失败: ' + error.message);
}
}
async verifyToken(token) {
try {
// 发送验证请求到后端
const response = await fetch('/api/admin/verify', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ token: token })
});
if (response.ok) {
const result = await response.json();
return result.success;
}
return false;
} catch (error) {
console.error('令牌验证错误:', error);
return false;
}
}
showManagement() {
document.getElementById('loginSection').style.display = 'none';
document.getElementById('managementSection').style.display = 'block';
// 自动加载账号列表
this.loadAccounts();
}
async loadAccounts() {
const accountsList = document.getElementById('accountsList');
// 显示加载状态(表格内)
const tbodyLoading = document.getElementById('accountsTbody');
if (tbodyLoading) {
tbodyLoading.innerHTML = `
|
正在加载账号列表...
|
`;
}
try {
const params = new URLSearchParams();
params.set('page', String(this.page));
params.set('page_size', String(this.pageSize));
if (this.query) params.set('q', this.query);
const response = await fetch(`/api/accounts/paged?${params.toString()}`, {
headers: {
'Authorization': `Bearer ${this.token}`
}
});
if (response.ok) {
const result = await response.json();
if (result.success) {
const items = (result.data && result.data.items) ? result.data.items : [];
this.total = (result.data && typeof result.data.total === 'number') ? result.data.total : items.length;
this.page = (result.data && typeof result.data.page === 'number') ? result.data.page : this.page;
this.pageSize = (result.data && typeof result.data.page_size === 'number') ? result.data.page_size : this.pageSize;
this.renderAccounts(items);
this.renderPager();
} else {
throw new Error(result.message);
}
} else {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
} catch (error) {
console.error('加载账号列表失败:', error);
accountsList.innerHTML = `
加载失败: ${error.message}
`;
}
}
async renderAccounts(accounts) {
const tbody = document.getElementById('accountsTbody');
if (!tbody) return;
if (accounts.length === 0) {
tbody.innerHTML = `
|
暂无账号数据
|
`;
return;
}
// 获取所有账户的标签信息
const accountsWithTags = await this.loadAccountsWithTags();
const accountsHtml = accounts.map((account, index) => {
const tags = accountsWithTags[account.email] || [];
const tagsHtml = tags.length > 0 ?
tags.map(tag => `${this.escapeHtml(tag)}`).join('') :
'无标签';
return `
| ${account.email} |
${tagsHtml} |
|
`;
}).join('');
tbody.innerHTML = accountsHtml;
}
// 已移除测试与系统配置相关功能
// 删除账户功能不在精简范围内,已移除调用
clearImport() {
document.getElementById('importTextarea').value = '';
document.getElementById('mergeMode').value = 'update';
this.showSuccess('已清空导入内容');
}
async executeImport() {
const textarea = document.getElementById('importTextarea');
const mergeMode = document.getElementById('mergeMode');
const importText = textarea.value.trim();
if (!importText) {
this.showError('请输入要导入的账户数据');
return;
}
try {
// 解析文本
const parseResponse = await fetch('/api/parse-import-text', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.token}`
},
body: JSON.stringify({ text: importText })
});
if (!parseResponse.ok) {
throw new Error(`解析失败: HTTP ${parseResponse.status}`);
}
const parseResult = await parseResponse.json();
if (!parseResult.success) {
throw new Error(parseResult.message);
}
// 执行导入
const importResponse = await fetch('/api/import', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.token}`
},
body: JSON.stringify({
accounts: parseResult.data.accounts, // 只提取accounts数组
merge_mode: mergeMode.value
})
});
if (!importResponse.ok) {
throw new Error(`导入失败: HTTP ${importResponse.status}`);
}
const importResult = await importResponse.json();
if (importResult.success) {
this.showSuccess(`导入完成! 新增: ${importResult.added_count}, 更新: ${importResult.updated_count}, 跳过: ${importResult.skipped_count}`);
// 清空导入内容
document.getElementById('importTextarea').value = '';
this.loadAccounts(); // 刷新账号列表
} else {
this.showError(`导入失败: ${importResult.message}`);
}
} catch (error) {
console.error('导入失败:', error);
this.showError(`导入失败: ${error.message}`);
}
}
async exportData() {
try {
const response = await fetch('/api/export');
if (response.ok) {
// 直接获取文本内容
const content = await response.text();
// 从响应头获取文件名
const contentDisposition = response.headers.get('Content-Disposition');
let filename = 'outlook_accounts_config.txt';
if (contentDisposition) {
const match = contentDisposition.match(/filename=(.+)/);
if (match) {
filename = match[1];
}
}
// 下载文件
this.downloadTextFile(content, filename);
this.showSuccess('数据导出成功');
} else {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
} catch (error) {
console.error('导出失败:', error);
this.showError(`导出失败: ${error.message}`);
}
}
downloadTextFile(content, filename) {
const blob = new Blob([content], { type: 'text/plain;charset=utf-8' });
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
}
logout() {
this.isAuthenticated = false;
this.token = '';
sessionStorage.removeItem('admin_token');
document.getElementById('managementSection').style.display = 'none';
document.getElementById('loginSection').style.display = 'block';
// 清空表单
document.getElementById('tokenInput').value = '';
this.showSuccess('已安全退出管理');
}
showSuccess(message) {
document.getElementById('successMessage').textContent = message;
const modal = new bootstrap.Modal(document.getElementById('successModal'));
modal.show();
}
showError(message) {
document.getElementById('errorMessage').textContent = message;
const modal = new bootstrap.Modal(document.getElementById('errorModal'));
modal.show();
}
// ==================== 标签管理功能 ====================
async loadAccountsWithTags() {
try {
const response = await fetch('/api/accounts/tags', {
headers: {
'Authorization': `Bearer ${this.token}`
}
});
if (response.ok) {
const result = await response.json();
if (result.success) {
return result.data.accounts || {};
}
}
} catch (error) {
console.error('加载账户标签失败:', error);
}
return {};
}
async showTagManagementDialog(email) {
// 显示标签管理对话框
const modal = new bootstrap.Modal(document.getElementById('tagManagementModal'));
const modalTitle = document.getElementById('tagManagementModalTitle');
const tagInput = document.getElementById('tagManagementInput');
const currentTagsDisplay = document.getElementById('tagManagementCurrentTags');
modalTitle.textContent = `管理标签 - ${email}`;
// 加载当前标签
try {
const response = await fetch(`/api/account/${encodeURIComponent(email)}/tags`, {
headers: {
'Authorization': `Bearer ${this.token}`
}
});
if (response.ok) {
const result = await response.json();
if (result.success) {
const tags = result.data.tags || [];
tagInput.value = tags.join(',');
if (tags.length > 0) {
const tagsHtml = tags.map(tag =>
`${this.escapeHtml(tag)}`
).join('');
currentTagsDisplay.innerHTML = tagsHtml;
} else {
currentTagsDisplay.innerHTML = '暂无标签';
}
}
}
} catch (error) {
console.error('加载账户标签失败:', error);
}
// 设置保存按钮事件
const saveBtn = document.getElementById('tagManagementSaveBtn');
saveBtn.onclick = () => this.saveAccountTagsFromDialog(email);
modal.show();
}
async saveAccountTagsFromDialog(email) {
const tagInput = document.getElementById('tagManagementInput');
const tagsText = tagInput.value.trim();
const tags = tagsText ? tagsText.split(',').map(tag => tag.trim()).filter(tag => tag) : [];
try {
const response = await fetch(`/api/account/${encodeURIComponent(email)}/tags`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.token}`
},
body: JSON.stringify({ email: email, tags: tags })
});
if (response.ok) {
const result = await response.json();
if (result.success) {
this.showSuccess('标签保存成功');
// 关闭对话框
const modal = bootstrap.Modal.getInstance(document.getElementById('tagManagementModal'));
modal.hide();
// 刷新账户列表
this.loadAccounts();
} else {
this.showError('保存失败: ' + result.message);
}
} else {
this.showError(`保存失败: HTTP ${response.status}`);
}
} catch (error) {
console.error('保存标签失败:', error);
this.showError('保存标签失败: ' + error.message);
}
}
async loadAllTags() {
const allTagsList = document.getElementById('allTagsList');
allTagsList.innerHTML = `
`;
try {
const response = await fetch('/api/accounts/tags', {
headers: {
'Authorization': `Bearer ${this.token}`
}
});
if (response.ok) {
const result = await response.json();
if (result.success) {
this.renderAllTags(result.data.tags || []);
} else {
throw new Error(result.message);
}
} else {
throw new Error(`HTTP ${response.status}`);
}
} catch (error) {
console.error('加载标签失败:', error);
allTagsList.innerHTML = `
`;
}
}
renderAllTags(tags) {
const allTagsList = document.getElementById('allTagsList');
if (tags.length === 0) {
allTagsList.innerHTML = `
`;
return;
}
const tagsHtml = tags.map(tag => `
${this.escapeHtml(tag)}
`).join('');
allTagsList.innerHTML = `
共 ${tags.length} 个标签:
${tagsHtml}
`;
}
async loadAccountsForTags() {
const tagAccountSelect = document.getElementById('tagAccountSelect');
try {
const response = await fetch('/api/accounts');
if (response.ok) {
const result = await response.json();
if (result.success) {
tagAccountSelect.innerHTML = '';
result.data.forEach(account => {
const option = document.createElement('option');
option.value = account.email;
option.textContent = account.email;
tagAccountSelect.appendChild(option);
});
}
}
} catch (error) {
console.error('加载账户列表失败:', error);
}
}
async loadAccountTags() {
const tagAccountSelect = document.getElementById('tagAccountSelect');
const accountTagsInput = document.getElementById('accountTagsInput');
const currentAccountTags = document.getElementById('currentAccountTags');
const currentTagsDisplay = document.getElementById('currentTagsDisplay');
const selectedEmail = tagAccountSelect.value;
if (!selectedEmail) {
accountTagsInput.value = '';
currentAccountTags.style.display = 'none';
return;
}
try {
const response = await fetch(`/api/account/${encodeURIComponent(selectedEmail)}/tags`, {
headers: {
'Authorization': `Bearer ${this.token}`
}
});
if (response.ok) {
const result = await response.json();
if (result.success) {
const tags = result.data.tags || [];
accountTagsInput.value = tags.join(',');
if (tags.length > 0) {
const tagsHtml = tags.map(tag =>
`${this.escapeHtml(tag)}`
).join('');
currentTagsDisplay.innerHTML = tagsHtml;
currentAccountTags.style.display = 'block';
} else {
currentAccountTags.style.display = 'none';
}
}
}
} catch (error) {
console.error('加载账户标签失败:', error);
this.showError('加载账户标签失败: ' + error.message);
}
}
async saveAccountTags() {
const tagAccountSelect = document.getElementById('tagAccountSelect');
const accountTagsInput = document.getElementById('accountTagsInput');
const selectedEmail = tagAccountSelect.value;
if (!selectedEmail) {
this.showError('请先选择账户');
return;
}
const tagsText = accountTagsInput.value.trim();
const tags = tagsText ? tagsText.split(',').map(tag => tag.trim()).filter(tag => tag) : [];
try {
const response = await fetch(`/api/account/${encodeURIComponent(selectedEmail)}/tags`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.token}`
},
body: JSON.stringify({ email: selectedEmail, tags: tags })
});
if (response.ok) {
const result = await response.json();
if (result.success) {
this.showSuccess('标签保存成功');
this.loadAccountTags(); // 刷新显示
this.loadAllTags(); // 刷新所有标签列表
} else {
this.showError('保存失败: ' + result.message);
}
} else {
this.showError(`保存失败: HTTP ${response.status}`);
}
} catch (error) {
console.error('保存标签失败:', error);
this.showError('保存标签失败: ' + error.message);
}
}
clearAccountTags() {
document.getElementById('accountTagsInput').value = '';
document.getElementById('currentAccountTags').style.display = 'none';
}
// ==================== 分页信息渲染 ====================
renderPager() {
const info = document.getElementById('accountsPagerInfo');
if (!info) return;
const maxPage = Math.max(1, Math.ceil(this.total / this.pageSize));
info.textContent = `第 ${this.page} / ${maxPage} 页,共 ${this.total} 条`;
const prevBtn = document.getElementById('prevPageBtn');
const nextBtn = document.getElementById('nextPageBtn');
if (prevBtn) prevBtn.disabled = this.page <= 1;
if (nextBtn) nextBtn.disabled = this.page >= maxPage;
}
// ==================== 辅助方法 ====================
escapeHtml(text) {
if (!text) return '';
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
formatDate(dateString) {
if (!dateString) return '未知时间';
try {
const date = new Date(dateString);
if (isNaN(date.getTime())) {
return dateString;
}
const now = new Date();
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
const messageDate = new Date(date.getFullYear(), date.getMonth(), date.getDate());
const timeDiff = today.getTime() - messageDate.getTime();
const daysDiff = Math.floor(timeDiff / (1000 * 3600 * 24));
if (daysDiff === 0) {
return date.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' });
} else if (daysDiff === 1) {
return '昨天';
} else if (daysDiff < 7) {
return `${daysDiff}天前`;
} else {
return date.toLocaleDateString('zh-CN', { month: 'short', day: 'numeric' });
}
} catch (error) {
console.error('Date formatting error:', error);
return dateString;
}
}
}
// 页面加载完成后初始化
document.addEventListener('DOMContentLoaded', function() {
window.adminManager = new AdminManager();
});