// ==================== 全局变量 ====================
const apiBase = location.origin;
let keywordsData = {};
let currentCookieId = '';
let editCookieId = '';
let authToken = localStorage.getItem('auth_token');
let dashboardData = {
accounts: [],
totalKeywords: 0
};
// 账号关键词缓存
let accountKeywordCache = {};
let cacheTimestamp = 0;
const CACHE_DURATION = 30000; // 30秒缓存
// ==================== 页面导航功能 ====================
// 菜单切换功能
function showSection(sectionName) {
console.log('切换到页面:', sectionName); // 调试信息
// 隐藏所有内容区域
document.querySelectorAll('.content-section').forEach(section => {
section.classList.remove('active');
});
// 移除所有菜单项的active状态
document.querySelectorAll('.nav-link').forEach(link => {
link.classList.remove('active');
});
// 显示选中的内容区域
const targetSection = document.getElementById(sectionName + '-section');
if (targetSection) {
targetSection.classList.add('active');
console.log('页面已激活:', sectionName + '-section'); // 调试信息
} else {
console.error('找不到页面元素:', sectionName + '-section'); // 调试信息
}
// 设置对应菜单项为active(修复event.target问题)
const menuLinks = document.querySelectorAll('.nav-link');
menuLinks.forEach(link => {
if (link.onclick && link.onclick.toString().includes(`showSection('${sectionName}')`)) {
link.classList.add('active');
}
});
// 根据不同section加载对应数据
switch (sectionName) {
case 'dashboard':
loadDashboard();
break;
case 'accounts':
loadCookies();
break;
case 'items':
loadItems();
break;
case 'auto-reply':
refreshAccountList();
break;
case 'cards':
loadCards();
break;
case 'auto-delivery':
loadDeliveryRules();
break;
case 'notification-channels':
loadNotificationChannels();
break;
case 'message-notifications':
loadMessageNotifications();
break;
case 'logs':
// 如果没有日志数据,则加载
setTimeout(() => {
if (!window.allLogs || window.allLogs.length === 0) {
refreshLogs();
}
}, 100);
break;
}
// 如果切换到非日志页面,停止自动刷新
if (sectionName !== 'logs' && window.autoRefreshInterval) {
clearInterval(window.autoRefreshInterval);
window.autoRefreshInterval = null;
const button = document.querySelector('#autoRefreshText');
const icon = button?.previousElementSibling;
if (button) {
button.textContent = '开启自动刷新';
if (icon) icon.className = 'bi bi-play-circle me-1';
}
}
}
// 移动端侧边栏切换
function toggleSidebar() {
document.getElementById('sidebar').classList.toggle('show');
}
// ==================== 仪表盘管理 ====================
// 加载仪表盘数据
async function loadDashboard() {
try {
toggleLoading(true);
// 获取账号列表
const cookiesResponse = await fetch(`${apiBase}/cookies/details`, {
headers: {
'Authorization': `Bearer ${authToken}`
}
});
if (cookiesResponse.ok) {
const cookiesData = await cookiesResponse.json();
// 为每个账号获取关键词信息
const accountsWithKeywords = await Promise.all(
cookiesData.map(async (account) => {
try {
const keywordsResponse = await fetch(`${apiBase}/keywords/${account.id}`, {
headers: {
'Authorization': `Bearer ${authToken}`
}
});
if (keywordsResponse.ok) {
const keywordsData = await keywordsResponse.json();
return {
...account,
keywords: keywordsData,
keywordCount: keywordsData.length
};
} else {
return {
...account,
keywords: [],
keywordCount: 0
};
}
} catch (error) {
console.error(`获取账号 ${account.id} 关键词失败:`, error);
return {
...account,
keywords: [],
keywordCount: 0
};
}
})
);
dashboardData.accounts = accountsWithKeywords;
// 计算统计数据
let totalKeywords = 0;
let activeAccounts = 0;
let enabledAccounts = 0;
accountsWithKeywords.forEach(account => {
const keywordCount = account.keywordCount || 0;
const isEnabled = account.enabled === undefined ? true : account.enabled;
if (isEnabled) {
enabledAccounts++;
totalKeywords += keywordCount;
if (keywordCount > 0) {
activeAccounts++;
}
}
});
dashboardData.totalKeywords = totalKeywords;
// 更新仪表盘显示
updateDashboardStats(accountsWithKeywords.length, totalKeywords, enabledAccounts);
updateDashboardAccountsList(accountsWithKeywords);
}
} catch (error) {
console.error('加载仪表盘数据失败:', error);
showToast('加载仪表盘数据失败', 'danger');
} finally {
toggleLoading(false);
}
}
// 更新仪表盘统计数据
function updateDashboardStats(totalAccounts, totalKeywords, enabledAccounts) {
document.getElementById('totalAccounts').textContent = totalAccounts;
document.getElementById('totalKeywords').textContent = totalKeywords;
document.getElementById('activeAccounts').textContent = enabledAccounts;
}
// 更新仪表盘账号列表
function updateDashboardAccountsList(accounts) {
const tbody = document.getElementById('dashboardAccountsList');
tbody.innerHTML = '';
if (accounts.length === 0) {
tbody.innerHTML = `
暂无账号数据
|
`;
return;
}
accounts.forEach(account => {
const keywordCount = account.keywordCount || 0;
const isEnabled = account.enabled === undefined ? true : account.enabled;
let status = '';
if (!isEnabled) {
status = '已禁用';
} else if (keywordCount > 0) {
status = '活跃';
} else {
status = '未配置';
}
const row = document.createElement('tr');
row.className = isEnabled ? '' : 'table-secondary';
row.innerHTML = `
${account.id}
${!isEnabled ? '' : ''}
|
${keywordCount} 个关键词
|
${status} |
${new Date().toLocaleString()}
|
`;
tbody.appendChild(row);
});
}
// ==================== 关键词缓存管理 ====================
// 获取账号关键词数量(带缓存)- 包含普通关键词和商品关键词
async function getAccountKeywordCount(accountId) {
const now = Date.now();
// 检查缓存
if (accountKeywordCache[accountId] && (now - cacheTimestamp) < CACHE_DURATION) {
return accountKeywordCache[accountId];
}
try {
const response = await fetch(`${apiBase}/keywords/${accountId}`, {
headers: {
'Authorization': `Bearer ${authToken}`
}
});
if (response.ok) {
const keywordsData = await response.json();
// 现在API返回的是包含普通关键词和商品关键词的完整列表
const count = keywordsData.length;
// 更新缓存
accountKeywordCache[accountId] = count;
cacheTimestamp = now;
return count;
} else {
return 0;
}
} catch (error) {
console.error(`获取账号 ${accountId} 关键词失败:`, error);
return 0;
}
}
// 清除关键词缓存
function clearKeywordCache() {
accountKeywordCache = {};
cacheTimestamp = 0;
}
// ==================== 账号列表管理 ====================
// 刷新账号列表(用于自动回复页面)
async function refreshAccountList() {
try {
toggleLoading(true);
// 获取账号列表
const response = await fetch(`${apiBase}/cookies/details`, {
headers: {
'Authorization': `Bearer ${authToken}`
}
});
if (response.ok) {
const accounts = await response.json();
const select = document.getElementById('accountSelect');
select.innerHTML = '';
// 为每个账号获取关键词数量
const accountsWithKeywords = await Promise.all(
accounts.map(async (account) => {
try {
const keywordsResponse = await fetch(`${apiBase}/keywords/${account.id}`, {
headers: {
'Authorization': `Bearer ${authToken}`
}
});
if (keywordsResponse.ok) {
const keywordsData = await keywordsResponse.json();
return {
...account,
keywords: keywordsData,
keywordCount: keywordsData.length
};
} else {
return {
...account,
keywordCount: 0
};
}
} catch (error) {
console.error(`获取账号 ${account.id} 关键词失败:`, error);
return {
...account,
keywordCount: 0
};
}
})
);
// 渲染账号选项(显示所有账号,但标识禁用状态)
if (accountsWithKeywords.length === 0) {
select.innerHTML = '';
return;
}
// 分组显示:先显示启用的账号,再显示禁用的账号
const enabledAccounts = accountsWithKeywords.filter(account => {
const enabled = account.enabled === undefined ? true : account.enabled;
console.log(`账号 ${account.id} 过滤状态: enabled=${account.enabled}, 判断为启用=${enabled}`);
return enabled;
});
const disabledAccounts = accountsWithKeywords.filter(account => {
const enabled = account.enabled === undefined ? true : account.enabled;
return !enabled;
});
// 渲染启用的账号
enabledAccounts.forEach(account => {
const option = document.createElement('option');
option.value = account.id;
// 根据关键词数量显示不同的图标和样式
let icon = '📝';
let status = '';
if (account.keywordCount === 0) {
icon = '⚪';
status = ' (未配置)';
} else if (account.keywordCount >= 5) {
icon = '🟢';
status = ` (${account.keywordCount} 个关键词)`;
} else {
icon = '🟡';
status = ` (${account.keywordCount} 个关键词)`;
}
option.textContent = `${icon} ${account.id}${status}`;
select.appendChild(option);
});
// 如果有禁用的账号,添加分隔线和禁用账号
if (disabledAccounts.length > 0) {
// 添加分隔线
const separatorOption = document.createElement('option');
separatorOption.disabled = true;
separatorOption.textContent = `--- 禁用账号 (${disabledAccounts.length} 个) ---`;
select.appendChild(separatorOption);
// 渲染禁用的账号
disabledAccounts.forEach(account => {
const option = document.createElement('option');
option.value = account.id;
// 禁用账号使用特殊图标和样式
let icon = '🔴';
let status = '';
if (account.keywordCount === 0) {
status = ' (未配置) [已禁用]';
} else {
status = ` (${account.keywordCount} 个关键词) [已禁用]`;
}
option.textContent = `${icon} ${account.id}${status}`;
option.style.color = '#6b7280';
option.style.fontStyle = 'italic';
select.appendChild(option);
});
}
console.log('账号列表刷新完成,关键词统计:', accountsWithKeywords.map(a => ({ id: a.id, keywords: a.keywordCount })));
} else {
showToast('获取账号列表失败', 'danger');
}
} catch (error) {
console.error('刷新账号列表失败:', error);
showToast('刷新账号列表失败', 'danger');
} finally {
toggleLoading(false);
}
}
// 加载账号关键词
async function loadAccountKeywords() {
const accountId = document.getElementById('accountSelect').value;
const keywordManagement = document.getElementById('keywordManagement');
if (!accountId) {
keywordManagement.style.display = 'none';
return;
}
try {
toggleLoading(true);
currentCookieId = accountId;
// 获取账号详情以检查状态
const accountResponse = await fetch(`${apiBase}/cookies/details`, {
headers: {
'Authorization': `Bearer ${authToken}`
}
});
let accountStatus = true; // 默认启用
if (accountResponse.ok) {
const accounts = await accountResponse.json();
const currentAccount = accounts.find(acc => acc.id === accountId);
accountStatus = currentAccount ? (currentAccount.enabled === undefined ? true : currentAccount.enabled) : true;
console.log(`加载关键词时账号 ${accountId} 状态: enabled=${currentAccount?.enabled}, accountStatus=${accountStatus}`); // 调试信息
}
const response = await fetch(`${apiBase}/keywords-with-item-id/${accountId}`, {
headers: {
'Authorization': `Bearer ${authToken}`
}
});
if (response.ok) {
const data = await response.json();
console.log('从服务器获取的关键词数据:', data); // 调试信息
// 后端返回的是 [{keyword, reply, item_id}, ...] 格式,直接使用
const formattedData = data;
console.log('格式化后的关键词数据:', formattedData); // 调试信息
keywordsData[accountId] = formattedData;
renderKeywordsList(formattedData);
// 加载商品列表
await loadItemsList(accountId);
// 更新账号徽章显示
updateAccountBadge(accountId, accountStatus);
keywordManagement.style.display = 'block';
} else {
showToast('加载关键词失败', 'danger');
}
} catch (error) {
console.error('加载关键词失败:', error);
showToast('加载关键词失败', 'danger');
} finally {
toggleLoading(false);
}
}
// 更新账号徽章显示
function updateAccountBadge(accountId, isEnabled) {
const badge = document.getElementById('currentAccountBadge');
if (!badge) return;
const statusIcon = isEnabled ? '🟢' : '🔴';
const statusText = isEnabled ? '启用' : '禁用';
const statusClass = isEnabled ? 'bg-success' : 'bg-warning';
badge.innerHTML = `
${statusIcon} ${accountId}
状态: ${statusText}
${!isEnabled ? ' (配置的关键词不会参与自动回复)' : ''}
`;
}
// 显示添加关键词表单
function showAddKeywordForm() {
const form = document.getElementById('addKeywordForm');
form.style.display = form.style.display === 'none' ? 'block' : 'none';
if (form.style.display === 'block') {
document.getElementById('newKeyword').focus();
}
}
// 加载商品列表
async function loadItemsList(accountId) {
try {
const response = await fetch(`${apiBase}/items/${accountId}`, {
headers: {
'Authorization': `Bearer ${authToken}`
}
});
if (response.ok) {
const data = await response.json();
const items = data.items || [];
// 更新商品选择下拉框
const selectElement = document.getElementById('newItemIdSelect');
if (selectElement) {
// 清空现有选项(保留第一个默认选项)
selectElement.innerHTML = '';
// 添加商品选项
items.forEach(item => {
const option = document.createElement('option');
option.value = item.item_id;
option.textContent = `${item.item_id} - ${item.item_title}`;
selectElement.appendChild(option);
});
}
console.log(`加载了 ${items.length} 个商品到选择列表`);
} else {
console.warn('加载商品列表失败:', response.status);
}
} catch (error) {
console.error('加载商品列表时发生错误:', error);
}
}
// 添加或更新关键词
async function addKeyword() {
const keyword = document.getElementById('newKeyword').value.trim();
const reply = document.getElementById('newReply').value.trim();
const itemId = document.getElementById('newItemIdSelect').value.trim();
if (!keyword || !reply) {
showToast('请填写关键词和回复内容', 'warning');
return;
}
if (!currentCookieId) {
showToast('请先选择账号', 'warning');
return;
}
// 检查是否为编辑模式
const isEditMode = typeof window.editingIndex !== 'undefined';
const actionText = isEditMode ? '更新' : '添加';
try {
toggleLoading(true);
// 获取当前关键词列表
let currentKeywords = [...(keywordsData[currentCookieId] || [])];
// 如果是编辑模式,先移除原关键词
if (isEditMode) {
currentKeywords.splice(window.editingIndex, 1);
}
// 准备要保存的关键词列表
let keywordsToSave = [...currentKeywords];
// 如果是编辑模式,先移除原关键词
if (isEditMode && typeof window.editingIndex !== 'undefined') {
keywordsToSave.splice(window.editingIndex, 1);
}
// 检查关键词是否已存在(考虑商品ID)
const existingKeyword = keywordsToSave.find(item =>
item.keyword === keyword &&
(item.item_id || '') === (itemId || '')
);
if (existingKeyword) {
const itemIdText = itemId ? `(商品ID: ${itemId})` : '(通用关键词)';
showToast(`关键词 "${keyword}" ${itemIdText} 已存在,请使用其他关键词或商品ID`, 'warning');
toggleLoading(false);
return;
}
// 添加新关键词或更新的关键词
const newKeyword = {
keyword: keyword,
reply: reply,
item_id: itemId || ''
};
keywordsToSave.push(newKeyword);
const response = await fetch(`${apiBase}/keywords-with-item-id/${currentCookieId}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${authToken}`
},
body: JSON.stringify({
keywords: keywordsToSave
})
});
if (response.ok) {
showToast(`✨ 关键词 "${keyword}" ${actionText}成功!`, 'success');
// 清空输入框并重置样式
const keywordInput = document.getElementById('newKeyword');
const replyInput = document.getElementById('newReply');
const selectElement = document.getElementById('newItemIdSelect');
const addBtn = document.querySelector('.add-btn');
keywordInput.value = '';
replyInput.value = '';
if (selectElement) {
selectElement.value = '';
}
keywordInput.style.borderColor = '#e5e7eb';
replyInput.style.borderColor = '#e5e7eb';
addBtn.style.opacity = '0.7';
addBtn.style.transform = 'scale(0.95)';
// 如果是编辑模式,重置编辑状态
if (isEditMode) {
delete window.editingIndex;
delete window.originalKeyword;
// 恢复添加按钮
addBtn.innerHTML = '添加';
addBtn.style.background = 'linear-gradient(135deg, #10b981 0%, #059669 100%)';
// 移除取消按钮
const cancelBtn = document.getElementById('cancelEditBtn');
if (cancelBtn) {
cancelBtn.remove();
}
}
// 聚焦到关键词输入框,方便连续添加
setTimeout(() => {
keywordInput.focus();
}, 100);
loadAccountKeywords(); // 重新加载关键词列表
clearKeywordCache(); // 清除缓存
} else {
const errorText = await response.text();
console.error('关键词添加失败:', errorText);
showToast('关键词添加失败', 'danger');
}
} catch (error) {
console.error('添加关键词失败:', error);
showToast('添加关键词失败', 'danger');
} finally {
toggleLoading(false);
}
}
// 渲染现代化关键词列表
function renderKeywordsList(keywords) {
console.log('渲染关键词列表:', keywords); // 调试信息
const container = document.getElementById('keywordsList');
if (!container) {
console.error('找不到关键词列表容器元素');
return;
}
container.innerHTML = '';
if (!keywords || keywords.length === 0) {
console.log('关键词列表为空,显示空状态');
container.innerHTML = `
还没有关键词
添加第一个关键词,让您的闲鱼店铺自动回复客户消息
`;
return;
}
console.log(`开始渲染 ${keywords.length} 个关键词`);
keywords.forEach((item, index) => {
console.log(`渲染关键词 ${index + 1}:`, item); // 调试信息
const keywordItem = document.createElement('div');
keywordItem.className = 'keyword-item';
// 商品ID显示
const itemIdDisplay = item.item_id ?
` 商品ID: ${item.item_id}` :
' 通用关键词';
keywordItem.innerHTML = `
`;
container.appendChild(keywordItem);
});
console.log('关键词列表渲染完成');
}
// 聚焦到关键词输入框
function focusKeywordInput() {
document.getElementById('newKeyword').focus();
}
// 编辑关键词 - 改进版本
function editKeyword(index) {
const keywords = keywordsData[currentCookieId] || [];
const keyword = keywords[index];
if (!keyword) {
showToast('关键词不存在', 'warning');
return;
}
// 将关键词信息填入输入框
document.getElementById('newKeyword').value = keyword.keyword;
document.getElementById('newReply').value = keyword.reply;
// 设置商品ID选择框
const selectElement = document.getElementById('newItemIdSelect');
if (selectElement) {
selectElement.value = keyword.item_id || '';
}
// 设置编辑模式标识
window.editingIndex = index;
window.originalKeyword = keyword.keyword;
window.originalItemId = keyword.item_id || '';
// 更新按钮文本和样式
const addBtn = document.querySelector('.add-btn');
addBtn.innerHTML = '更新';
addBtn.style.background = 'linear-gradient(135deg, #f59e0b 0%, #d97706 100%)';
// 显示取消按钮
showCancelEditButton();
// 聚焦到关键词输入框并选中文本
setTimeout(() => {
const keywordInput = document.getElementById('newKeyword');
keywordInput.focus();
keywordInput.select();
}, 100);
showToast('📝 编辑模式:修改后点击"更新"按钮保存', 'info');
}
// 显示取消编辑按钮
function showCancelEditButton() {
// 检查是否已存在取消按钮
if (document.getElementById('cancelEditBtn')) {
return;
}
const addBtn = document.querySelector('.add-btn');
const cancelBtn = document.createElement('button');
cancelBtn.id = 'cancelEditBtn';
cancelBtn.className = 'btn btn-outline-secondary';
cancelBtn.style.marginLeft = '0.5rem';
cancelBtn.innerHTML = '取消';
cancelBtn.onclick = cancelEdit;
addBtn.parentNode.appendChild(cancelBtn);
}
// 取消编辑
function cancelEdit() {
// 清空输入框
document.getElementById('newKeyword').value = '';
document.getElementById('newReply').value = '';
// 清空商品ID选择框
const selectElement = document.getElementById('newItemIdSelect');
if (selectElement) {
selectElement.value = '';
}
// 重置编辑状态
delete window.editingIndex;
delete window.originalKeyword;
delete window.originalItemId;
// 恢复添加按钮
const addBtn = document.querySelector('.add-btn');
addBtn.innerHTML = '添加';
addBtn.style.background = 'linear-gradient(135deg, #10b981 0%, #059669 100%)';
// 移除取消按钮
const cancelBtn = document.getElementById('cancelEditBtn');
if (cancelBtn) {
cancelBtn.remove();
}
showToast('已取消编辑', 'info');
}
// 删除关键词
async function deleteKeyword(cookieId, index) {
if (!confirm('确定要删除这个关键词吗?')) {
return;
}
try {
toggleLoading(true);
// 获取当前关键词列表
const currentKeywords = keywordsData[cookieId] || [];
// 移除指定索引的关键词
currentKeywords.splice(index, 1);
// 更新服务器
const response = await fetch(`${apiBase}/keywords-with-item-id/${cookieId}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${authToken}`
},
body: JSON.stringify({
keywords: currentKeywords
})
});
if (response.ok) {
showToast('关键词删除成功', 'success');
keywordsData[cookieId] = currentKeywords;
renderKeywordsList(currentKeywords);
clearKeywordCache(); // 清除缓存
} else {
const errorText = await response.text();
console.error('关键词删除失败:', errorText);
showToast('关键词删除失败', 'danger');
}
} catch (error) {
console.error('删除关键词失败:', error);
showToast('删除关键词删除失败', 'danger');
} finally {
toggleLoading(false);
}
}
// ==================== 通用工具函数 ====================
// 显示/隐藏加载动画
function toggleLoading(show) {
document.getElementById('loading').classList.toggle('d-none', !show);
}
// 显示提示消息
function showToast(message, type = 'success') {
const toastContainer = document.querySelector('.toast-container');
const toast = document.createElement('div');
toast.className = `toast align-items-center text-white bg-${type} border-0`;
toast.setAttribute('role', 'alert');
toast.setAttribute('aria-live', 'assertive');
toast.setAttribute('aria-atomic', 'true');
toast.innerHTML = `
`;
toastContainer.appendChild(toast);
const bsToast = new bootstrap.Toast(toast, { delay: 3000 });
bsToast.show();
// 自动移除
toast.addEventListener('hidden.bs.toast', () => {
toast.remove();
});
}
// 错误处理
async function handleApiError(err) {
console.error(err);
showToast(err.message || '操作失败', 'danger');
toggleLoading(false);
}
// API请求包装
async function fetchJSON(url, opts = {}) {
toggleLoading(true);
try {
// 添加认证头
if (authToken) {
opts.headers = opts.headers || {};
opts.headers['Authorization'] = `Bearer ${authToken}`;
}
const res = await fetch(url, opts);
if (res.status === 401) {
// 未授权,跳转到登录页面
localStorage.removeItem('auth_token');
window.location.href = '/';
return;
}
if (!res.ok) {
let errorMessage = `HTTP ${res.status}`;
try {
const errorText = await res.text();
if (errorText) {
// 尝试解析JSON错误信息
try {
const errorJson = JSON.parse(errorText);
errorMessage = errorJson.detail || errorJson.message || errorText;
} catch {
errorMessage = errorText;
}
}
} catch {
errorMessage = `HTTP ${res.status} ${res.statusText}`;
}
throw new Error(errorMessage);
}
const data = await res.json();
toggleLoading(false);
return data;
} catch (err) {
handleApiError(err);
throw err;
}
}
// ==================== Cookie管理 ====================
// 加载Cookie列表
async function loadCookies() {
try {
toggleLoading(true);
const tbody = document.querySelector('#cookieTable tbody');
tbody.innerHTML = '';
const cookieDetails = await fetchJSON(apiBase + '/cookies/details');
if (cookieDetails.length === 0) {
tbody.innerHTML = `
暂无账号
请添加新的闲鱼账号开始使用
|
`;
return;
}
// 为每个账号获取关键词数量和默认回复设置并渲染
const accountsWithKeywords = await Promise.all(
cookieDetails.map(async (cookie) => {
try {
// 获取关键词数量
const keywordsResponse = await fetch(`${apiBase}/keywords/${cookie.id}`, {
headers: { 'Authorization': `Bearer ${authToken}` }
});
let keywordCount = 0;
if (keywordsResponse.ok) {
const keywordsData = await keywordsResponse.json();
keywordCount = keywordsData.length;
}
// 获取默认回复设置
const defaultReplyResponse = await fetch(`${apiBase}/default-replies/${cookie.id}`, {
headers: { 'Authorization': `Bearer ${authToken}` }
});
let defaultReply = { enabled: false, reply_content: '' };
if (defaultReplyResponse.ok) {
defaultReply = await defaultReplyResponse.json();
}
// 获取AI回复设置
const aiReplyResponse = await fetch(`${apiBase}/ai-reply-settings/${cookie.id}`, {
headers: { 'Authorization': `Bearer ${authToken}` }
});
let aiReply = { ai_enabled: false, model_name: 'qwen-plus' };
if (aiReplyResponse.ok) {
aiReply = await aiReplyResponse.json();
}
return {
...cookie,
keywordCount: keywordCount,
defaultReply: defaultReply,
aiReply: aiReply
};
} catch (error) {
return {
...cookie,
keywordCount: 0,
defaultReply: { enabled: false, reply_content: '' },
aiReply: { ai_enabled: false, model_name: 'qwen-plus' }
};
}
})
);
accountsWithKeywords.forEach(cookie => {
// 使用数据库中的实际状态,默认为启用
const isEnabled = cookie.enabled === undefined ? true : cookie.enabled;
console.log(`账号 ${cookie.id} 状态: enabled=${cookie.enabled}, isEnabled=${isEnabled}`); // 调试信息
const tr = document.createElement('tr');
tr.className = `account-row ${isEnabled ? 'enabled' : 'disabled'}`;
// 默认回复状态标签
const defaultReplyBadge = cookie.defaultReply.enabled ?
'启用' :
'禁用';
// AI回复状态标签
const aiReplyBadge = cookie.aiReply.ai_enabled ?
'AI启用' :
'AI禁用';
// 自动确认发货状态(默认开启)
const autoConfirm = cookie.auto_confirm === undefined ? true : cookie.auto_confirm;
tr.innerHTML = `
${cookie.id}
|
${cookie.value || '未设置'}
|
${cookie.keywordCount} 个关键词
|
|
${defaultReplyBadge}
|
${aiReplyBadge}
|
|
|
`;
tbody.appendChild(tr);
});
// 为Cookie值添加点击复制功能
document.querySelectorAll('.cookie-value').forEach(element => {
element.style.cursor = 'pointer';
element.addEventListener('click', function() {
const cookieValue = this.textContent;
if (cookieValue && cookieValue !== '未设置') {
navigator.clipboard.writeText(cookieValue).then(() => {
showToast('Cookie已复制到剪贴板', 'success');
}).catch(() => {
showToast('复制失败,请手动复制', 'error');
});
}
});
});
} catch (err) {
// 错误已在fetchJSON中处理
} finally {
toggleLoading(false);
}
}
// 复制Cookie
function copyCookie(id, value) {
if (!value || value === '未设置') {
showToast('该账号暂无Cookie值', 'warning');
return;
}
navigator.clipboard.writeText(value).then(() => {
showToast(`账号 "${id}" 的Cookie已复制到剪贴板`, 'success');
}).catch(() => {
// 降级方案:创建临时文本框
const textArea = document.createElement('textarea');
textArea.value = value;
document.body.appendChild(textArea);
textArea.select();
try {
document.execCommand('copy');
showToast(`账号 "${id}" 的Cookie已复制到剪贴板`, 'success');
} catch (err) {
showToast('复制失败,请手动复制', 'error');
}
document.body.removeChild(textArea);
});
}
// 删除Cookie
async function delCookie(id) {
if (!confirm(`确定要删除账号 "${id}" 吗?此操作不可恢复。`)) return;
try {
await fetchJSON(apiBase + `/cookies/${id}`, { method: 'DELETE' });
showToast(`账号 "${id}" 已删除`, 'success');
loadCookies();
} catch (err) {
// 错误已在fetchJSON中处理
}
}
/**
* 内联编辑Cookie
* @param {string} id - 账号ID
* @param {string} currentValue - 当前Cookie值
* @param {Event} event - 点击事件对象
*/
function editCookieInline(id, currentValue, event) {
const row = event.target.closest('tr');
const cookieValueCell = row.querySelector('.cookie-value');
const originalContent = cookieValueCell.innerHTML;
// 存储原始数据到全局变量,避免HTML注入问题
window.editingCookieData = {
id: id,
originalContent: originalContent,
originalValue: currentValue || ''
};
// 创建编辑界面容器
const editContainer = document.createElement('div');
editContainer.className = 'd-flex gap-2';
// 创建输入框
const input = document.createElement('input');
input.type = 'text';
input.className = 'form-control form-control-sm';
input.id = `edit-${id}`;
input.value = currentValue || '';
input.placeholder = '输入新的Cookie值';
// 创建保存按钮
const saveBtn = document.createElement('button');
saveBtn.className = 'btn btn-sm btn-success';
saveBtn.title = '保存';
saveBtn.innerHTML = '';
saveBtn.onclick = () => saveCookieInline(id);
// 创建取消按钮
const cancelBtn = document.createElement('button');
cancelBtn.className = 'btn btn-sm btn-secondary';
cancelBtn.title = '取消';
cancelBtn.innerHTML = '';
cancelBtn.onclick = () => cancelCookieEdit(id);
// 组装编辑界面
editContainer.appendChild(input);
editContainer.appendChild(saveBtn);
editContainer.appendChild(cancelBtn);
// 替换原内容
cookieValueCell.innerHTML = '';
cookieValueCell.appendChild(editContainer);
// 聚焦输入框
input.focus();
input.select();
// 添加键盘事件监听
input.addEventListener('keydown', function (e) {
if (e.key === 'Enter') {
e.preventDefault();
saveCookieInline(id);
} else if (e.key === 'Escape') {
e.preventDefault();
cancelCookieEdit(id);
}
});
// 禁用该行的其他按钮
const actionButtons = row.querySelectorAll('.btn-group button');
actionButtons.forEach(btn => btn.disabled = true);
}
// 保存内联编辑的Cookie
async function saveCookieInline(id) {
const input = document.getElementById(`edit-${id}`);
const newValue = input.value.trim();
if (!newValue) {
showToast('Cookie值不能为空', 'warning');
return;
}
try {
toggleLoading(true);
await fetchJSON(apiBase + `/cookies/${id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
id: id,
value: newValue
})
});
showToast(`账号 "${id}" Cookie已更新`, 'success');
loadCookies(); // 重新加载列表
} catch (err) {
console.error('Cookie更新失败:', err);
showToast(`Cookie更新失败: ${err.message || '未知错误'}`, 'danger');
// 恢复原内容
cancelCookieEdit(id);
} finally {
toggleLoading(false);
}
}
// 取消Cookie编辑
function cancelCookieEdit(id) {
if (!window.editingCookieData || window.editingCookieData.id !== id) {
console.error('编辑数据不存在');
return;
}
const row = document.querySelector(`#edit-${id}`).closest('tr');
const cookieValueCell = row.querySelector('.cookie-value');
// 恢复原内容
cookieValueCell.innerHTML = window.editingCookieData.originalContent;
// 恢复按钮状态
const actionButtons = row.querySelectorAll('.btn-group button');
actionButtons.forEach(btn => btn.disabled = false);
// 清理全局数据
delete window.editingCookieData;
}
// 切换账号启用/禁用状态
async function toggleAccountStatus(accountId, enabled) {
try {
toggleLoading(true);
// 这里需要调用后端API来更新账号状态
// 由于当前后端可能没有enabled字段,我们先在前端模拟
// 实际项目中需要后端支持
const response = await fetch(`${apiBase}/cookies/${accountId}/status`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${authToken}`
},
body: JSON.stringify({ enabled: enabled })
});
if (response.ok) {
showToast(`账号 "${accountId}" 已${enabled ? '启用' : '禁用'}`, 'success');
// 清除相关缓存,确保数据一致性
clearKeywordCache();
// 更新界面显示
updateAccountRowStatus(accountId, enabled);
// 刷新自动回复页面的账号列表
refreshAccountList();
// 如果禁用的账号在自动回复页面被选中,更新显示
const accountSelect = document.getElementById('accountSelect');
if (accountSelect && accountSelect.value === accountId) {
if (!enabled) {
// 更新徽章显示禁用状态
updateAccountBadge(accountId, false);
showToast('账号已禁用,配置的关键词不会参与自动回复', 'warning');
} else {
// 更新徽章显示启用状态
updateAccountBadge(accountId, true);
showToast('账号已启用,配置的关键词将参与自动回复', 'success');
}
}
} else {
// 如果后端不支持,先在前端模拟
console.warn('后端暂不支持账号状态切换,使用前端模拟');
showToast(`账号 "${accountId}" 已${enabled ? '启用' : '禁用'} (前端模拟)`, enabled ? 'success' : 'warning');
updateAccountRowStatus(accountId, enabled);
}
} catch (error) {
console.error('切换账号状态失败:', error);
// 后端不支持时的降级处理
showToast(`账号 "${accountId}" 已${enabled ? '启用' : '禁用'} (本地模拟)`, enabled ? 'success' : 'warning');
updateAccountRowStatus(accountId, enabled);
// 恢复切换按钮状态
const toggle = document.querySelector(`input[onchange*="${accountId}"]`);
if (toggle) {
toggle.checked = enabled;
}
} finally {
toggleLoading(false);
}
}
// 更新账号行的状态显示
function updateAccountRowStatus(accountId, enabled) {
const toggle = document.querySelector(`input[onchange*="${accountId}"]`);
if (!toggle) return;
const row = toggle.closest('tr');
const statusBadge = row.querySelector('.status-badge');
const actionButtons = row.querySelectorAll('.btn-group .btn:not(.btn-outline-info):not(.btn-outline-danger)');
// 更新行样式
row.className = `account-row ${enabled ? 'enabled' : 'disabled'}`;
// 更新状态徽章
statusBadge.className = `status-badge ${enabled ? 'enabled' : 'disabled'}`;
statusBadge.title = enabled ? '账号已启用' : '账号已禁用';
statusBadge.innerHTML = `
`;
// 更新按钮状态(只禁用编辑Cookie按钮,其他按钮保持可用)
actionButtons.forEach(btn => {
if (btn.onclick && btn.onclick.toString().includes('editCookieInline')) {
btn.disabled = !enabled;
}
// 设置自动回复按钮始终可用,但更新提示文本
if (btn.onclick && btn.onclick.toString().includes('goToAutoReply')) {
btn.title = enabled ? '设置自动回复' : '配置关键词 (账号已禁用)';
}
});
// 更新切换按钮的提示
const label = toggle.closest('.status-toggle');
label.title = enabled ? '点击禁用' : '点击启用';
}
// 切换自动确认发货状态
async function toggleAutoConfirm(accountId, enabled) {
try {
toggleLoading(true);
const response = await fetch(`${apiBase}/cookies/${accountId}/auto-confirm`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${authToken}`
},
body: JSON.stringify({ auto_confirm: enabled })
});
if (response.ok) {
const result = await response.json();
showToast(result.message, 'success');
// 更新界面显示
updateAutoConfirmRowStatus(accountId, enabled);
} else {
const error = await response.json();
showToast(error.detail || '更新自动确认发货设置失败', 'error');
// 恢复切换按钮状态
const toggle = document.querySelector(`input[onchange*="toggleAutoConfirm('${accountId}'"]`);
if (toggle) {
toggle.checked = !enabled;
}
}
} catch (error) {
console.error('切换自动确认发货状态失败:', error);
showToast('网络错误,请稍后重试', 'error');
// 恢复切换按钮状态
const toggle = document.querySelector(`input[onchange*="toggleAutoConfirm('${accountId}'"]`);
if (toggle) {
toggle.checked = !enabled;
}
} finally {
toggleLoading(false);
}
}
// 更新自动确认发货行状态
function updateAutoConfirmRowStatus(accountId, enabled) {
const row = document.querySelector(`tr:has(input[onchange*="toggleAutoConfirm('${accountId}'"])`);
if (!row) return;
const statusBadge = row.querySelector('.status-badge:has(i.bi-truck, i.bi-truck-flatbed)');
const toggle = row.querySelector(`input[onchange*="toggleAutoConfirm('${accountId}'"]`);
if (statusBadge && toggle) {
// 更新状态徽章
statusBadge.className = `status-badge ${enabled ? 'enabled' : 'disabled'}`;
statusBadge.title = enabled ? '自动确认发货已开启' : '自动确认发货已关闭';
statusBadge.innerHTML = `
`;
// 更新切换按钮的提示
const label = toggle.closest('.status-toggle');
label.title = enabled ? '点击关闭自动确认发货' : '点击开启自动确认发货';
}
}
// 跳转到自动回复页面并选择指定账号
function goToAutoReply(accountId) {
// 切换到自动回复页面
showSection('auto-reply');
// 设置账号选择器的值
setTimeout(() => {
const accountSelect = document.getElementById('accountSelect');
if (accountSelect) {
accountSelect.value = accountId;
// 触发change事件来加载关键词
loadAccountKeywords();
}
}, 100);
showToast(`已切换到自动回复页面,账号 "${accountId}" 已选中`, 'info');
}
// 登出功能
async function logout() {
try {
if (authToken) {
await fetch('/logout', {
method: 'POST',
headers: {
'Authorization': `Bearer ${authToken}`
}
});
}
localStorage.removeItem('auth_token');
window.location.href = '/';
} catch (err) {
console.error('登出失败:', err);
localStorage.removeItem('auth_token');
window.location.href = '/';
}
}
// 检查认证状态
async function checkAuth() {
if (!authToken) {
window.location.href = '/';
return false;
}
try {
const response = await fetch('/verify', {
headers: {
'Authorization': `Bearer ${authToken}`
}
});
const result = await response.json();
if (!result.authenticated) {
localStorage.removeItem('auth_token');
window.location.href = '/';
return false;
}
// 检查是否为管理员,显示管理员菜单和功能
if (result.username === 'admin') {
const adminMenuSection = document.getElementById('adminMenuSection');
if (adminMenuSection) {
adminMenuSection.style.display = 'block';
}
// 显示备份管理功能
const backupManagement = document.getElementById('backup-management');
if (backupManagement) {
backupManagement.style.display = 'block';
}
}
return true;
} catch (err) {
localStorage.removeItem('auth_token');
window.location.href = '/';
return false;
}
}
// ==================== 应用初始化 ====================
// 初始化事件监听
document.addEventListener('DOMContentLoaded', async () => {
// 首先检查认证状态
const isAuthenticated = await checkAuth();
if (!isAuthenticated) return;
// 添加Cookie表单提交
document.getElementById('addForm').addEventListener('submit', async (e) => {
e.preventDefault();
const id = document.getElementById('cookieId').value.trim();
const value = document.getElementById('cookieValue').value.trim();
if (!id || !value) return;
try {
await fetchJSON(apiBase + '/cookies', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id, value })
});
document.getElementById('cookieId').value = '';
document.getElementById('cookieValue').value = '';
showToast(`账号 "${id}" 添加成功`);
loadCookies();
} catch (err) {
// 错误已在fetchJSON中处理
}
});
// 增强的键盘快捷键和用户体验
document.getElementById('newKeyword')?.addEventListener('keypress', function (e) {
if (e.key === 'Enter') {
e.preventDefault();
document.getElementById('newReply').focus();
}
});
document.getElementById('newReply')?.addEventListener('keypress', function (e) {
if (e.key === 'Enter') {
e.preventDefault();
addKeyword();
}
});
// ESC键取消编辑
document.addEventListener('keydown', function (e) {
if (e.key === 'Escape' && typeof window.editingIndex !== 'undefined') {
e.preventDefault();
cancelEdit();
}
});
// 输入框实时验证和提示
document.getElementById('newKeyword')?.addEventListener('input', function (e) {
const value = e.target.value.trim();
const addBtn = document.querySelector('.add-btn');
const replyInput = document.getElementById('newReply');
if (value.length > 0) {
e.target.style.borderColor = '#10b981';
if (replyInput.value.trim().length > 0) {
addBtn.style.opacity = '1';
addBtn.style.transform = 'scale(1)';
}
} else {
e.target.style.borderColor = '#e5e7eb';
addBtn.style.opacity = '0.7';
addBtn.style.transform = 'scale(0.95)';
}
});
document.getElementById('newReply')?.addEventListener('input', function (e) {
const value = e.target.value.trim();
const addBtn = document.querySelector('.add-btn');
const keywordInput = document.getElementById('newKeyword');
if (value.length > 0) {
e.target.style.borderColor = '#10b981';
if (keywordInput.value.trim().length > 0) {
addBtn.style.opacity = '1';
addBtn.style.transform = 'scale(1)';
}
} else {
e.target.style.borderColor = '#e5e7eb';
addBtn.style.opacity = '0.7';
addBtn.style.transform = 'scale(0.95)';
}
});
// 初始加载仪表盘
loadDashboard();
// 点击侧边栏外部关闭移动端菜单
document.addEventListener('click', function (e) {
const sidebar = document.getElementById('sidebar');
const toggle = document.querySelector('.mobile-toggle');
if (window.innerWidth <= 768 &&
!sidebar.contains(e.target) &&
!toggle.contains(e.target) &&
sidebar.classList.contains('show')) {
sidebar.classList.remove('show');
}
});
});
// ==================== 默认回复管理功能 ====================
// 打开默认回复管理器
async function openDefaultReplyManager() {
try {
await loadDefaultReplies();
const modal = new bootstrap.Modal(document.getElementById('defaultReplyModal'));
modal.show();
} catch (error) {
console.error('打开默认回复管理器失败:', error);
showToast('打开默认回复管理器失败', 'danger');
}
}
// 加载默认回复列表
async function loadDefaultReplies() {
try {
// 获取所有账号
const accountsResponse = await fetch(`${apiBase}/cookies`, {
headers: {
'Authorization': `Bearer ${authToken}`
}
});
if (!accountsResponse.ok) {
throw new Error('获取账号列表失败');
}
const accounts = await accountsResponse.json();
// 获取所有默认回复设置
const repliesResponse = await fetch(`${apiBase}/default-replies`, {
headers: {
'Authorization': `Bearer ${authToken}`
}
});
let defaultReplies = {};
if (repliesResponse.ok) {
defaultReplies = await repliesResponse.json();
}
renderDefaultRepliesList(accounts, defaultReplies);
} catch (error) {
console.error('加载默认回复列表失败:', error);
showToast('加载默认回复列表失败', 'danger');
}
}
// 渲染默认回复列表
function renderDefaultRepliesList(accounts, defaultReplies) {
const tbody = document.getElementById('defaultReplyTableBody');
tbody.innerHTML = '';
if (accounts.length === 0) {
tbody.innerHTML = `
暂无账号数据
请先添加账号
|
`;
return;
}
accounts.forEach(accountId => {
const replySettings = defaultReplies[accountId] || { enabled: false, reply_content: '' };
const tr = document.createElement('tr');
// 状态标签
const statusBadge = replySettings.enabled ?
'启用' :
'禁用';
// 回复内容预览
let contentPreview = replySettings.reply_content || '未设置';
if (contentPreview.length > 50) {
contentPreview = contentPreview.substring(0, 50) + '...';
}
tr.innerHTML = `
${accountId}
|
${statusBadge} |
${contentPreview}
|
|
`;
tbody.appendChild(tr);
});
}
// 编辑默认回复
async function editDefaultReply(accountId) {
try {
// 获取当前设置
const response = await fetch(`${apiBase}/default-replies/${accountId}`, {
headers: {
'Authorization': `Bearer ${authToken}`
}
});
let settings = { enabled: false, reply_content: '' };
if (response.ok) {
settings = await response.json();
}
// 填充编辑表单
document.getElementById('editAccountId').value = accountId;
document.getElementById('editAccountIdDisplay').value = accountId;
document.getElementById('editDefaultReplyEnabled').checked = settings.enabled;
document.getElementById('editReplyContent').value = settings.reply_content || '';
// 根据启用状态显示/隐藏内容输入框
toggleReplyContentVisibility();
// 显示编辑模态框
const modal = new bootstrap.Modal(document.getElementById('editDefaultReplyModal'));
modal.show();
} catch (error) {
console.error('获取默认回复设置失败:', error);
showToast('获取默认回复设置失败', 'danger');
}
}
// 切换回复内容输入框的显示/隐藏
function toggleReplyContentVisibility() {
const enabled = document.getElementById('editDefaultReplyEnabled').checked;
const contentGroup = document.getElementById('editReplyContentGroup');
contentGroup.style.display = enabled ? 'block' : 'none';
}
// 保存默认回复设置
async function saveDefaultReply() {
try {
const accountId = document.getElementById('editAccountId').value;
const enabled = document.getElementById('editDefaultReplyEnabled').checked;
const replyContent = document.getElementById('editReplyContent').value;
if (enabled && !replyContent.trim()) {
showToast('启用默认回复时必须设置回复内容', 'warning');
return;
}
const data = {
enabled: enabled,
reply_content: enabled ? replyContent : null
};
const response = await fetch(`${apiBase}/default-replies/${accountId}`, {
method: 'PUT',
headers: {
'Authorization': `Bearer ${authToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
if (response.ok) {
showToast('默认回复设置保存成功', 'success');
bootstrap.Modal.getInstance(document.getElementById('editDefaultReplyModal')).hide();
loadDefaultReplies(); // 刷新列表
loadCookies(); // 刷新账号列表以更新默认回复状态显示
} else {
const error = await response.text();
showToast(`保存失败: ${error}`, 'danger');
}
} catch (error) {
console.error('保存默认回复设置失败:', error);
showToast('保存默认回复设置失败', 'danger');
}
}
/**
* 测试默认回复(占位函数)
* @param {string} accountId - 账号ID
*/
function testDefaultReply(accountId) {
console.log('测试默认回复功能,账号ID:', accountId);
showToast('测试功能开发中...', 'info');
}
// ==================== AI回复配置相关函数 ====================
// 配置AI回复
async function configAIReply(accountId) {
try {
// 获取当前AI回复设置
const settings = await fetchJSON(`${apiBase}/ai-reply-settings/${accountId}`);
// 填充表单
document.getElementById('aiConfigAccountId').value = accountId;
document.getElementById('aiConfigAccountIdDisplay').value = accountId;
document.getElementById('aiReplyEnabled').checked = settings.ai_enabled;
// 处理模型名称
const modelSelect = document.getElementById('aiModelName');
const customModelInput = document.getElementById('customModelName');
const modelName = settings.model_name;
// 检查是否是预设模型
const presetModels = ['qwen-plus', 'qwen-turbo', 'qwen-max', 'gpt-3.5-turbo', 'gpt-4'];
if (presetModels.includes(modelName)) {
modelSelect.value = modelName;
customModelInput.style.display = 'none';
customModelInput.value = '';
} else {
// 自定义模型
modelSelect.value = 'custom';
customModelInput.style.display = 'block';
customModelInput.value = modelName;
}
document.getElementById('aiBaseUrl').value = settings.base_url;
document.getElementById('aiApiKey').value = settings.api_key;
document.getElementById('maxDiscountPercent').value = settings.max_discount_percent;
document.getElementById('maxDiscountAmount').value = settings.max_discount_amount;
document.getElementById('maxBargainRounds').value = settings.max_bargain_rounds;
document.getElementById('customPrompts').value = settings.custom_prompts;
// 切换设置显示状态
toggleAIReplySettings();
// 显示模态框
const modal = new bootstrap.Modal(document.getElementById('aiReplyConfigModal'));
modal.show();
} catch (error) {
console.error('获取AI回复设置失败:', error);
showToast('获取AI回复设置失败', 'danger');
}
}
// 切换AI回复设置显示
function toggleAIReplySettings() {
const enabled = document.getElementById('aiReplyEnabled').checked;
const settingsDiv = document.getElementById('aiReplySettings');
const bargainSettings = document.getElementById('bargainSettings');
const promptSettings = document.getElementById('promptSettings');
const testArea = document.getElementById('testArea');
if (enabled) {
settingsDiv.style.display = 'block';
bargainSettings.style.display = 'block';
promptSettings.style.display = 'block';
testArea.style.display = 'block';
} else {
settingsDiv.style.display = 'none';
bargainSettings.style.display = 'none';
promptSettings.style.display = 'none';
testArea.style.display = 'none';
}
}
// 保存AI回复配置
async function saveAIReplyConfig() {
try {
const accountId = document.getElementById('aiConfigAccountId').value;
const enabled = document.getElementById('aiReplyEnabled').checked;
// 如果启用AI回复,验证必填字段
if (enabled) {
const apiKey = document.getElementById('aiApiKey').value.trim();
if (!apiKey) {
showToast('请输入API密钥', 'warning');
return;
}
// 验证自定义提示词格式
const customPrompts = document.getElementById('customPrompts').value.trim();
if (customPrompts) {
try {
JSON.parse(customPrompts);
} catch (e) {
showToast('自定义提示词格式错误,请检查JSON格式', 'warning');
return;
}
}
}
// 获取模型名称
let modelName = document.getElementById('aiModelName').value;
if (modelName === 'custom') {
const customModelName = document.getElementById('customModelName').value.trim();
if (!customModelName) {
showToast('请输入自定义模型名称', 'warning');
return;
}
modelName = customModelName;
}
// 构建设置对象
const settings = {
ai_enabled: enabled,
model_name: modelName,
api_key: document.getElementById('aiApiKey').value,
base_url: document.getElementById('aiBaseUrl').value,
max_discount_percent: parseInt(document.getElementById('maxDiscountPercent').value),
max_discount_amount: parseInt(document.getElementById('maxDiscountAmount').value),
max_bargain_rounds: parseInt(document.getElementById('maxBargainRounds').value),
custom_prompts: document.getElementById('customPrompts').value
};
// 保存设置
const response = await fetch(`${apiBase}/ai-reply-settings/${accountId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${authToken}`
},
body: JSON.stringify(settings)
});
if (response.ok) {
showToast('AI回复配置保存成功', 'success');
bootstrap.Modal.getInstance(document.getElementById('aiReplyConfigModal')).hide();
loadCookies(); // 刷新账号列表以更新AI回复状态显示
} else {
const error = await response.text();
showToast(`保存失败: ${error}`, 'danger');
}
} catch (error) {
console.error('保存AI回复配置失败:', error);
showToast('保存AI回复配置失败', 'danger');
}
}
// 测试AI回复
async function testAIReply() {
try {
const accountId = document.getElementById('aiConfigAccountId').value;
const testMessage = document.getElementById('testMessage').value.trim();
const testItemPrice = document.getElementById('testItemPrice').value;
if (!testMessage) {
showToast('请输入测试消息', 'warning');
return;
}
// 构建测试数据
const testData = {
message: testMessage,
item_title: '测试商品',
item_price: parseFloat(testItemPrice) || 100,
item_desc: '这是一个用于测试AI回复功能的商品'
};
// 显示加载状态
const testResult = document.getElementById('testResult');
const testReplyContent = document.getElementById('testReplyContent');
testResult.style.display = 'block';
testReplyContent.innerHTML = ' 正在生成AI回复...';
// 调用测试API
const response = await fetch(`${apiBase}/ai-reply-test/${accountId}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${authToken}`
},
body: JSON.stringify(testData)
});
if (response.ok) {
const result = await response.json();
testReplyContent.innerHTML = result.reply;
showToast('AI回复测试成功', 'success');
} else {
const error = await response.text();
testReplyContent.innerHTML = `测试失败: ${error}`;
showToast(`测试失败: ${error}`, 'danger');
}
} catch (error) {
console.error('测试AI回复失败:', error);
const testReplyContent = document.getElementById('testReplyContent');
testReplyContent.innerHTML = `测试失败: ${error.message}`;
showToast('测试AI回复失败', 'danger');
}
}
// 切换自定义模型输入框的显示/隐藏
function toggleCustomModelInput() {
const modelSelect = document.getElementById('aiModelName');
const customModelInput = document.getElementById('customModelName');
if (modelSelect.value === 'custom') {
customModelInput.style.display = 'block';
customModelInput.focus();
} else {
customModelInput.style.display = 'none';
customModelInput.value = '';
}
}
// 监听默认回复启用状态变化
document.addEventListener('DOMContentLoaded', function() {
const enabledCheckbox = document.getElementById('editDefaultReplyEnabled');
if (enabledCheckbox) {
enabledCheckbox.addEventListener('change', toggleReplyContentVisibility);
}
});
// ==================== 通知渠道管理功能 ====================
// 通知渠道类型配置
const channelTypeConfigs = {
qq: {
title: 'QQ通知',
description: '需要添加QQ号 3668943488
为好友才能正常接收消息通知',
icon: 'bi-chat-dots-fill',
color: 'primary',
fields: [
{
id: 'qq_number',
label: '接收QQ号码',
type: 'text',
placeholder: '输入QQ号码',
required: true,
help: '用于接收通知消息的QQ号码'
}
]
},
dingtalk: {
title: '钉钉通知',
description: '请设置钉钉机器人Webhook URL,支持自定义机器人和群机器人',
icon: 'bi-bell-fill',
color: 'info',
fields: [
{
id: 'webhook_url',
label: '钉钉机器人Webhook URL',
type: 'url',
placeholder: 'https://oapi.dingtalk.com/robot/send?access_token=...',
required: true,
help: '钉钉机器人的Webhook地址'
},
{
id: 'secret',
label: '加签密钥(可选)',
type: 'text',
placeholder: '输入加签密钥',
required: false,
help: '如果机器人开启了加签验证,请填写密钥'
}
]
},
email: {
title: '邮件通知',
description: '通过SMTP服务器发送邮件通知,支持各种邮箱服务商',
icon: 'bi-envelope-fill',
color: 'success',
fields: [
{
id: 'smtp_server',
label: 'SMTP服务器',
type: 'text',
placeholder: 'smtp.gmail.com',
required: true,
help: '邮箱服务商的SMTP服务器地址'
},
{
id: 'smtp_port',
label: 'SMTP端口',
type: 'number',
placeholder: '587',
required: true,
help: '通常为587(TLS)或465(SSL)'
},
{
id: 'email_user',
label: '发送邮箱',
type: 'email',
placeholder: 'your-email@gmail.com',
required: true,
help: '用于发送通知的邮箱地址'
},
{
id: 'email_password',
label: '邮箱密码/授权码',
type: 'password',
placeholder: '输入密码或授权码',
required: true,
help: '邮箱密码或应用专用密码'
},
{
id: 'recipient_email',
label: '接收邮箱',
type: 'email',
placeholder: 'recipient@example.com',
required: true,
help: '用于接收通知的邮箱地址'
}
]
},
webhook: {
title: 'Webhook通知',
description: '通过HTTP POST请求发送通知到自定义的Webhook地址',
icon: 'bi-link-45deg',
color: 'warning',
fields: [
{
id: 'webhook_url',
label: 'Webhook URL',
type: 'url',
placeholder: 'https://your-server.com/webhook',
required: true,
help: '接收通知的Webhook地址'
},
{
id: 'http_method',
label: 'HTTP方法',
type: 'select',
options: [
{ value: 'POST', text: 'POST' },
{ value: 'PUT', text: 'PUT' }
],
required: true,
help: '发送请求使用的HTTP方法'
},
{
id: 'headers',
label: '自定义请求头(可选)',
type: 'textarea',
placeholder: '{"Authorization": "Bearer token", "Content-Type": "application/json"}',
required: false,
help: 'JSON格式的自定义请求头'
}
]
},
wechat: {
title: '微信通知',
description: '通过企业微信机器人发送通知消息',
icon: 'bi-wechat',
color: 'success',
fields: [
{
id: 'webhook_url',
label: '企业微信机器人Webhook URL',
type: 'url',
placeholder: 'https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=...',
required: true,
help: '企业微信群机器人的Webhook地址'
}
]
},
telegram: {
title: 'Telegram通知',
description: '通过Telegram机器人发送通知消息',
icon: 'bi-telegram',
color: 'primary',
fields: [
{
id: 'bot_token',
label: 'Bot Token',
type: 'text',
placeholder: '123456789:ABCdefGHIjklMNOpqrsTUVwxyz',
required: true,
help: '从@BotFather获取的机器人Token'
},
{
id: 'chat_id',
label: 'Chat ID',
type: 'text',
placeholder: '123456789 或 @channel_name',
required: true,
help: '接收消息的用户ID或频道名'
}
]
}
};
// 显示添加渠道模态框
function showAddChannelModal(type) {
const config = channelTypeConfigs[type];
if (!config) {
showToast('不支持的通知渠道类型', 'danger');
return;
}
// 设置模态框标题和描述
document.getElementById('addChannelModalTitle').textContent = `添加${config.title}`;
document.getElementById('channelTypeDescription').innerHTML = config.description;
document.getElementById('channelType').value = type;
// 生成配置字段
const fieldsContainer = document.getElementById('channelConfigFields');
fieldsContainer.innerHTML = '';
config.fields.forEach(field => {
const fieldHtml = generateFieldHtml(field, '');
fieldsContainer.insertAdjacentHTML('beforeend', fieldHtml);
});
// 显示模态框
const modal = new bootstrap.Modal(document.getElementById('addChannelModal'));
modal.show();
}
// 生成表单字段HTML
function generateFieldHtml(field, prefix) {
const fieldId = prefix + field.id;
let inputHtml = '';
switch (field.type) {
case 'select':
inputHtml = `';
break;
case 'textarea':
inputHtml = ``;
break;
default:
inputHtml = ``;
}
return `
${inputHtml}
${field.help ? `${field.help}` : ''}
`;
}
// 保存通知渠道
async function saveNotificationChannel() {
const type = document.getElementById('channelType').value;
const name = document.getElementById('channelName').value;
const enabled = document.getElementById('channelEnabled').checked;
if (!name.trim()) {
showToast('请输入渠道名称', 'warning');
return;
}
const config = channelTypeConfigs[type];
if (!config) {
showToast('无效的渠道类型', 'danger');
return;
}
// 收集配置数据
const configData = {};
let hasError = false;
config.fields.forEach(field => {
const element = document.getElementById(field.id);
const value = element.value.trim();
if (field.required && !value) {
showToast(`请填写${field.label}`, 'warning');
hasError = true;
return;
}
if (value) {
configData[field.id] = value;
}
});
if (hasError) return;
try {
const response = await fetch(`${apiBase}/notification-channels`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${authToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: name,
type: type,
config: JSON.stringify(configData),
enabled: enabled
})
});
if (response.ok) {
showToast('通知渠道添加成功', 'success');
const modal = bootstrap.Modal.getInstance(document.getElementById('addChannelModal'));
modal.hide();
loadNotificationChannels();
} else {
const error = await response.text();
showToast(`添加失败: ${error}`, 'danger');
}
} catch (error) {
console.error('添加通知渠道失败:', error);
showToast('添加通知渠道失败', 'danger');
}
}
// 加载通知渠道列表
async function loadNotificationChannels() {
try {
const response = await fetch(`${apiBase}/notification-channels`, {
headers: {
'Authorization': `Bearer ${authToken}`
}
});
if (!response.ok) {
throw new Error('获取通知渠道失败');
}
const channels = await response.json();
renderNotificationChannels(channels);
} catch (error) {
console.error('加载通知渠道失败:', error);
showToast('加载通知渠道失败', 'danger');
}
}
// 渲染通知渠道列表
function renderNotificationChannels(channels) {
const tbody = document.getElementById('channelsTableBody');
tbody.innerHTML = '';
if (channels.length === 0) {
tbody.innerHTML = `
暂无通知渠道
点击上方按钮添加通知渠道
|
`;
return;
}
channels.forEach(channel => {
const tr = document.createElement('tr');
const statusBadge = channel.enabled ?
'启用' :
'禁用';
// 获取渠道类型配置(处理类型映射)
let channelType = channel.type;
if (channelType === 'ding_talk') {
channelType = 'dingtalk'; // 兼容旧的类型名
}
const typeConfig = channelTypeConfigs[channelType];
const typeDisplay = typeConfig ? typeConfig.title : channel.type;
const typeColor = typeConfig ? typeConfig.color : 'secondary';
// 解析并显示配置信息
let configDisplay = '';
try {
const configData = JSON.parse(channel.config || '{}');
const configEntries = Object.entries(configData);
if (configEntries.length > 0) {
configDisplay = configEntries.map(([key, value]) => {
// 隐藏敏感信息
if (key.includes('password') || key.includes('token') || key.includes('secret')) {
return `${key}: ****`;
}
// 截断过长的值
const displayValue = value.length > 30 ? value.substring(0, 30) + '...' : value;
return `${key}: ${displayValue}`;
}).join('
');
} else {
configDisplay = channel.config || '无配置';
}
} catch (e) {
// 兼容旧格式
configDisplay = channel.config || '无配置';
if (configDisplay.length > 30) {
configDisplay = configDisplay.substring(0, 30) + '...';
}
}
tr.innerHTML = `
${channel.id} |
${channel.name}
|
${typeDisplay} |
${configDisplay} |
${statusBadge} |
|
`;
tbody.appendChild(tr);
});
}
// 删除通知渠道
async function deleteNotificationChannel(channelId) {
if (!confirm('确定要删除这个通知渠道吗?')) {
return;
}
try {
const response = await fetch(`${apiBase}/notification-channels/${channelId}`, {
method: 'DELETE',
headers: {
'Authorization': `Bearer ${authToken}`
}
});
if (response.ok) {
showToast('通知渠道删除成功', 'success');
loadNotificationChannels();
} else {
const error = await response.text();
showToast(`删除失败: ${error}`, 'danger');
}
} catch (error) {
console.error('删除通知渠道失败:', error);
showToast('删除通知渠道失败', 'danger');
}
}
// 编辑通知渠道
async function editNotificationChannel(channelId) {
try {
// 获取渠道详情
const response = await fetch(`${apiBase}/notification-channels`, {
headers: {
'Authorization': `Bearer ${authToken}`
}
});
if (!response.ok) {
throw new Error('获取通知渠道失败');
}
const channels = await response.json();
const channel = channels.find(c => c.id === channelId);
if (!channel) {
showToast('通知渠道不存在', 'danger');
return;
}
// 处理类型映射
let channelType = channel.type;
if (channelType === 'ding_talk') {
channelType = 'dingtalk'; // 兼容旧的类型名
}
const config = channelTypeConfigs[channelType];
if (!config) {
showToast('不支持的渠道类型', 'danger');
return;
}
// 填充基本信息
document.getElementById('editChannelId').value = channel.id;
document.getElementById('editChannelType').value = channelType; // 使用映射后的类型
document.getElementById('editChannelName').value = channel.name;
document.getElementById('editChannelEnabled').checked = channel.enabled;
// 解析配置数据
let configData = {};
try {
configData = JSON.parse(channel.config || '{}');
} catch (e) {
// 兼容旧格式(直接字符串)
if (channel.type === 'qq') {
configData = { qq_number: channel.config };
} else if (channel.type === 'dingtalk' || channel.type === 'ding_talk') {
configData = { webhook_url: channel.config };
} else {
configData = { config: channel.config };
}
}
// 生成编辑字段
const fieldsContainer = document.getElementById('editChannelConfigFields');
fieldsContainer.innerHTML = '';
config.fields.forEach(field => {
const fieldHtml = generateFieldHtml(field, 'edit_');
fieldsContainer.insertAdjacentHTML('beforeend', fieldHtml);
// 填充现有值
const element = document.getElementById('edit_' + field.id);
if (element && configData[field.id]) {
element.value = configData[field.id];
}
});
// 显示编辑模态框
const modal = new bootstrap.Modal(document.getElementById('editChannelModal'));
modal.show();
} catch (error) {
console.error('编辑通知渠道失败:', error);
showToast('编辑通知渠道失败', 'danger');
}
}
// 更新通知渠道
async function updateNotificationChannel() {
const channelId = document.getElementById('editChannelId').value;
const type = document.getElementById('editChannelType').value;
const name = document.getElementById('editChannelName').value;
const enabled = document.getElementById('editChannelEnabled').checked;
if (!name.trim()) {
showToast('请输入渠道名称', 'warning');
return;
}
const config = channelTypeConfigs[type];
if (!config) {
showToast('无效的渠道类型', 'danger');
return;
}
// 收集配置数据
const configData = {};
let hasError = false;
config.fields.forEach(field => {
const element = document.getElementById('edit_' + field.id);
const value = element.value.trim();
if (field.required && !value) {
showToast(`请填写${field.label}`, 'warning');
hasError = true;
return;
}
if (value) {
configData[field.id] = value;
}
});
if (hasError) return;
try {
const response = await fetch(`${apiBase}/notification-channels/${channelId}`, {
method: 'PUT',
headers: {
'Authorization': `Bearer ${authToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: name,
config: JSON.stringify(configData),
enabled: enabled
})
});
if (response.ok) {
showToast('通知渠道更新成功', 'success');
const modal = bootstrap.Modal.getInstance(document.getElementById('editChannelModal'));
modal.hide();
loadNotificationChannels();
} else {
const error = await response.text();
showToast(`更新失败: ${error}`, 'danger');
}
} catch (error) {
console.error('更新通知渠道失败:', error);
showToast('更新通知渠道失败', 'danger');
}
}
// ==================== 消息通知配置功能 ====================
// 加载消息通知配置
async function loadMessageNotifications() {
try {
// 获取所有账号
const accountsResponse = await fetch(`${apiBase}/cookies`, {
headers: {
'Authorization': `Bearer ${authToken}`
}
});
if (!accountsResponse.ok) {
throw new Error('获取账号列表失败');
}
const accounts = await accountsResponse.json();
// 获取所有通知配置
const notificationsResponse = await fetch(`${apiBase}/message-notifications`, {
headers: {
'Authorization': `Bearer ${authToken}`
}
});
let notifications = {};
if (notificationsResponse.ok) {
notifications = await notificationsResponse.json();
}
renderMessageNotifications(accounts, notifications);
} catch (error) {
console.error('加载消息通知配置失败:', error);
showToast('加载消息通知配置失败', 'danger');
}
}
// 渲染消息通知配置
function renderMessageNotifications(accounts, notifications) {
const tbody = document.getElementById('notificationsTableBody');
tbody.innerHTML = '';
if (accounts.length === 0) {
tbody.innerHTML = `
暂无账号数据
请先添加账号
|
`;
return;
}
accounts.forEach(accountId => {
const accountNotifications = notifications[accountId] || [];
const tr = document.createElement('tr');
let channelsList = '';
if (accountNotifications.length > 0) {
channelsList = accountNotifications.map(n =>
`${n.channel_name}`
).join('');
} else {
channelsList = '未配置';
}
const status = accountNotifications.some(n => n.enabled) ?
'启用' :
'禁用';
tr.innerHTML = `
${accountId} |
${channelsList} |
${status} |
${accountNotifications.length > 0 ? `
` : ''}
|
`;
tbody.appendChild(tr);
});
}
// 配置账号通知
async function configAccountNotification(accountId) {
try {
// 获取所有通知渠道
const channelsResponse = await fetch(`${apiBase}/notification-channels`, {
headers: {
'Authorization': `Bearer ${authToken}`
}
});
if (!channelsResponse.ok) {
throw new Error('获取通知渠道失败');
}
const channels = await channelsResponse.json();
if (channels.length === 0) {
showToast('请先添加通知渠道', 'warning');
return;
}
// 获取当前账号的通知配置
const notificationResponse = await fetch(`${apiBase}/message-notifications/${accountId}`, {
headers: {
'Authorization': `Bearer ${authToken}`
}
});
let currentNotifications = [];
if (notificationResponse.ok) {
currentNotifications = await notificationResponse.json();
}
// 填充表单
document.getElementById('configAccountId').value = accountId;
document.getElementById('displayAccountId').value = accountId;
// 填充通知渠道选项
const channelSelect = document.getElementById('notificationChannel');
channelSelect.innerHTML = '';
// 获取当前配置的第一个通知渠道(如果存在)
const currentNotification = currentNotifications.length > 0 ? currentNotifications[0] : null;
channels.forEach(channel => {
if (channel.enabled) {
const option = document.createElement('option');
option.value = channel.id;
option.textContent = `${channel.name} (${channel.config})`;
if (currentNotification && currentNotification.channel_id === channel.id) {
option.selected = true;
}
channelSelect.appendChild(option);
}
});
// 设置启用状态
document.getElementById('notificationEnabled').checked =
currentNotification ? currentNotification.enabled : true;
// 显示配置模态框
const modal = new bootstrap.Modal(document.getElementById('configNotificationModal'));
modal.show();
} catch (error) {
console.error('配置账号通知失败:', error);
showToast('配置账号通知失败', 'danger');
}
}
// 删除账号通知配置
async function deleteAccountNotification(accountId) {
if (!confirm(`确定要删除账号 ${accountId} 的通知配置吗?`)) {
return;
}
try {
const response = await fetch(`${apiBase}/message-notifications/account/${accountId}`, {
method: 'DELETE',
headers: {
'Authorization': `Bearer ${authToken}`
}
});
if (response.ok) {
showToast('通知配置删除成功', 'success');
loadMessageNotifications();
} else {
const error = await response.text();
showToast(`删除失败: ${error}`, 'danger');
}
} catch (error) {
console.error('删除通知配置失败:', error);
showToast('删除通知配置失败', 'danger');
}
}
// 保存账号通知配置
async function saveAccountNotification() {
const accountId = document.getElementById('configAccountId').value;
const channelId = document.getElementById('notificationChannel').value;
const enabled = document.getElementById('notificationEnabled').checked;
if (!channelId) {
showToast('请选择通知渠道', 'warning');
return;
}
try {
const response = await fetch(`${apiBase}/message-notifications/${accountId}`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${authToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
channel_id: parseInt(channelId),
enabled: enabled
})
});
if (response.ok) {
showToast('通知配置保存成功', 'success');
const modal = bootstrap.Modal.getInstance(document.getElementById('configNotificationModal'));
modal.hide();
loadMessageNotifications();
} else {
const error = await response.text();
showToast(`保存失败: ${error}`, 'danger');
}
} catch (error) {
console.error('保存通知配置失败:', error);
showToast('保存通知配置失败', 'danger');
}
}
// ==================== 卡券管理功能 ====================
// 加载卡券列表
async function loadCards() {
try {
const response = await fetch(`${apiBase}/cards`, {
headers: {
'Authorization': `Bearer ${authToken}`
}
});
if (response.ok) {
const cards = await response.json();
renderCardsList(cards);
updateCardsStats(cards);
} else {
showToast('加载卡券列表失败', 'danger');
}
} catch (error) {
console.error('加载卡券列表失败:', error);
showToast('加载卡券列表失败', 'danger');
}
}
// 渲染卡券列表
function renderCardsList(cards) {
const tbody = document.getElementById('cardsTableBody');
if (cards.length === 0) {
tbody.innerHTML = `
暂无卡券数据
点击"添加卡券"开始创建您的第一个卡券
|
`;
return;
}
tbody.innerHTML = '';
cards.forEach(card => {
const tr = document.createElement('tr');
// 类型标签
let typeBadge = '';
switch(card.type) {
case 'api':
typeBadge = 'API接口';
break;
case 'text':
typeBadge = '固定文字';
break;
case 'data':
typeBadge = '批量数据';
break;
}
// 状态标签
const statusBadge = card.enabled ?
'启用' :
'禁用';
// 数据量显示
let dataCount = '-';
if (card.type === 'data' && card.data_content) {
const lines = card.data_content.split('\n').filter(line => line.trim());
dataCount = lines.length;
} else if (card.type === 'api') {
dataCount = '∞';
} else if (card.type === 'text') {
dataCount = '1';
}
// 延时时间显示
const delayDisplay = card.delay_seconds > 0 ?
`${card.delay_seconds}秒` :
'立即';
// 规格信息显示
let specDisplay = '普通卡券';
if (card.is_multi_spec && card.spec_name && card.spec_value) {
specDisplay = `${card.spec_name}: ${card.spec_value}`;
}
tr.innerHTML = `
${card.name}
${card.description ? `${card.description}` : ''}
|
${typeBadge} |
${specDisplay} |
${dataCount} |
${delayDisplay} |
${statusBadge} |
${new Date(card.created_at).toLocaleString('zh-CN')}
|
|
`;
tbody.appendChild(tr);
});
}
// 更新卡券统计
function updateCardsStats(cards) {
const totalCards = cards.length;
const apiCards = cards.filter(card => card.type === 'api').length;
const textCards = cards.filter(card => card.type === 'text').length;
const dataCards = cards.filter(card => card.type === 'data').length;
document.getElementById('totalCards').textContent = totalCards;
document.getElementById('apiCards').textContent = apiCards;
document.getElementById('textCards').textContent = textCards;
document.getElementById('dataCards').textContent = dataCards;
}
// 显示添加卡券模态框
function showAddCardModal() {
document.getElementById('addCardForm').reset();
toggleCardTypeFields();
const modal = new bootstrap.Modal(document.getElementById('addCardModal'));
modal.show();
}
// 切换卡券类型字段显示
function toggleCardTypeFields() {
const cardType = document.getElementById('cardType').value;
document.getElementById('apiFields').style.display = cardType === 'api' ? 'block' : 'none';
document.getElementById('textFields').style.display = cardType === 'text' ? 'block' : 'none';
document.getElementById('dataFields').style.display = cardType === 'data' ? 'block' : 'none';
}
// 切换多规格字段显示
function toggleMultiSpecFields() {
const isMultiSpec = document.getElementById('isMultiSpec').checked;
document.getElementById('multiSpecFields').style.display = isMultiSpec ? 'block' : 'none';
}
// 切换编辑多规格字段显示
function toggleEditMultiSpecFields() {
const checkbox = document.getElementById('editIsMultiSpec');
const fieldsDiv = document.getElementById('editMultiSpecFields');
if (!checkbox) {
console.error('编辑多规格开关元素未找到');
return;
}
if (!fieldsDiv) {
console.error('编辑多规格字段容器未找到');
return;
}
const isMultiSpec = checkbox.checked;
const displayStyle = isMultiSpec ? 'block' : 'none';
console.log('toggleEditMultiSpecFields - 多规格状态:', isMultiSpec);
console.log('toggleEditMultiSpecFields - 设置显示样式:', displayStyle);
fieldsDiv.style.display = displayStyle;
// 验证设置是否生效
console.log('toggleEditMultiSpecFields - 实际显示样式:', fieldsDiv.style.display);
}
// 清空添加卡券表单
function clearAddCardForm() {
try {
// 安全地清空表单字段
const setElementValue = (id, value) => {
const element = document.getElementById(id);
if (element) {
if (element.type === 'checkbox') {
element.checked = value;
} else {
element.value = value;
}
} else {
console.warn(`Element with id '${id}' not found`);
}
};
const setElementDisplay = (id, display) => {
const element = document.getElementById(id);
if (element) {
element.style.display = display;
} else {
console.warn(`Element with id '${id}' not found`);
}
};
// 清空基本字段
setElementValue('cardName', '');
setElementValue('cardType', 'text');
setElementValue('cardDescription', '');
setElementValue('cardDelaySeconds', '0');
setElementValue('isMultiSpec', false);
setElementValue('specName', '');
setElementValue('specValue', '');
// 隐藏多规格字段
setElementDisplay('multiSpecFields', 'none');
// 清空类型相关字段
setElementValue('textContent', '');
setElementValue('dataContent', '');
setElementValue('apiUrl', '');
setElementValue('apiMethod', 'GET');
setElementValue('apiHeaders', '');
setElementValue('apiParams', '');
setElementValue('apiTimeout', '10');
// 重置字段显示
toggleCardTypeFields();
} catch (error) {
console.error('清空表单时出错:', error);
}
}
// 保存卡券
async function saveCard() {
try {
const cardType = document.getElementById('cardType').value;
const cardName = document.getElementById('cardName').value;
if (!cardType || !cardName) {
showToast('请填写必填字段', 'warning');
return;
}
// 检查多规格设置
const isMultiSpec = document.getElementById('isMultiSpec').checked;
const specName = document.getElementById('specName').value;
const specValue = document.getElementById('specValue').value;
// 验证多规格字段
if (isMultiSpec && (!specName || !specValue)) {
showToast('多规格卡券必须填写规格名称和规格值', 'warning');
return;
}
const cardData = {
name: cardName,
type: cardType,
description: document.getElementById('cardDescription').value,
delay_seconds: parseInt(document.getElementById('cardDelaySeconds').value) || 0,
enabled: true,
is_multi_spec: isMultiSpec,
spec_name: isMultiSpec ? specName : null,
spec_value: isMultiSpec ? specValue : null
};
// 根据类型添加特定配置
switch(cardType) {
case 'api':
// 验证和解析JSON字段
let headers = '{}';
let params = '{}';
try {
const headersInput = document.getElementById('apiHeaders').value.trim();
if (headersInput) {
JSON.parse(headersInput); // 验证JSON格式
headers = headersInput;
}
} catch (e) {
showToast('请求头格式错误,请输入有效的JSON', 'warning');
return;
}
try {
const paramsInput = document.getElementById('apiParams').value.trim();
if (paramsInput) {
JSON.parse(paramsInput); // 验证JSON格式
params = paramsInput;
}
} catch (e) {
showToast('请求参数格式错误,请输入有效的JSON', 'warning');
return;
}
cardData.api_config = {
url: document.getElementById('apiUrl').value,
method: document.getElementById('apiMethod').value,
timeout: parseInt(document.getElementById('apiTimeout').value),
headers: headers,
params: params
};
break;
case 'text':
cardData.text_content = document.getElementById('textContent').value;
break;
case 'data':
cardData.data_content = document.getElementById('dataContent').value;
break;
}
const response = await fetch(`${apiBase}/cards`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${authToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(cardData)
});
if (response.ok) {
showToast('卡券保存成功', 'success');
bootstrap.Modal.getInstance(document.getElementById('addCardModal')).hide();
// 清空表单
clearAddCardForm();
loadCards();
} else {
let errorMessage = '保存失败';
try {
const errorData = await response.json();
errorMessage = errorData.error || errorData.detail || errorMessage;
} catch (e) {
// 如果不是JSON格式,尝试获取文本
try {
const errorText = await response.text();
errorMessage = errorText || errorMessage;
} catch (e2) {
errorMessage = `HTTP ${response.status}: ${response.statusText}`;
}
}
showToast(`保存失败: ${errorMessage}`, 'danger');
}
} catch (error) {
console.error('保存卡券失败:', error);
showToast(`网络错误: ${error.message}`, 'danger');
}
}
// ==================== 自动发货功能 ====================
// 加载发货规则列表
async function loadDeliveryRules() {
try {
const response = await fetch(`${apiBase}/delivery-rules`, {
headers: {
'Authorization': `Bearer ${authToken}`
}
});
if (response.ok) {
const rules = await response.json();
renderDeliveryRulesList(rules);
updateDeliveryStats(rules);
// 同时加载卡券列表用于下拉选择
loadCardsForSelect();
} else {
showToast('加载发货规则失败', 'danger');
}
} catch (error) {
console.error('加载发货规则失败:', error);
showToast('加载发货规则失败', 'danger');
}
}
// 渲染发货规则列表
function renderDeliveryRulesList(rules) {
const tbody = document.getElementById('deliveryRulesTableBody');
if (rules.length === 0) {
tbody.innerHTML = `
暂无发货规则
点击"添加规则"开始配置自动发货规则
|
`;
return;
}
tbody.innerHTML = '';
rules.forEach(rule => {
const tr = document.createElement('tr');
// 状态标签
const statusBadge = rule.enabled ?
'启用' :
'禁用';
// 卡券类型标签
let cardTypeBadge = '未知';
if (rule.card_type) {
switch(rule.card_type) {
case 'api':
cardTypeBadge = 'API接口';
break;
case 'text':
cardTypeBadge = '固定文字';
break;
case 'data':
cardTypeBadge = '批量数据';
break;
}
}
tr.innerHTML = `
${rule.keyword}
${rule.description ? `${rule.description}` : ''}
|
${rule.card_name || '未知卡券'}
${rule.is_multi_spec && rule.spec_name && rule.spec_value ?
` ${rule.spec_name}: ${rule.spec_value}` :
''}
|
${cardTypeBadge} |
${rule.delivery_count || 1}
|
${statusBadge} |
${rule.delivery_times || 0}
|
|
`;
tbody.appendChild(tr);
});
}
// 更新发货统计
function updateDeliveryStats(rules) {
const totalRules = rules.length;
const activeRules = rules.filter(rule => rule.enabled).length;
const todayDeliveries = 0; // 需要从后端获取今日发货统计
const totalDeliveries = rules.reduce((sum, rule) => sum + (rule.delivery_times || 0), 0);
document.getElementById('totalRules').textContent = totalRules;
document.getElementById('activeRules').textContent = activeRules;
document.getElementById('todayDeliveries').textContent = todayDeliveries;
document.getElementById('totalDeliveries').textContent = totalDeliveries;
}
// 显示添加发货规则模态框
function showAddDeliveryRuleModal() {
document.getElementById('addDeliveryRuleForm').reset();
loadCardsForSelect(); // 加载卡券选项
const modal = new bootstrap.Modal(document.getElementById('addDeliveryRuleModal'));
modal.show();
}
// 加载卡券列表用于下拉选择
async function loadCardsForSelect() {
try {
const response = await fetch(`${apiBase}/cards`, {
headers: {
'Authorization': `Bearer ${authToken}`
}
});
if (response.ok) {
const cards = await response.json();
const select = document.getElementById('selectedCard');
// 清空现有选项
select.innerHTML = '';
cards.forEach(card => {
if (card.enabled) { // 只显示启用的卡券
const option = document.createElement('option');
option.value = card.id;
// 构建显示文本
let displayText = card.name;
// 添加类型信息
const typeText = card.type === 'api' ? 'API' : card.type === 'text' ? '固定文字' : '批量数据';
displayText += ` (${typeText})`;
// 添加规格信息
if (card.is_multi_spec && card.spec_name && card.spec_value) {
displayText += ` [${card.spec_name}:${card.spec_value}]`;
}
option.textContent = displayText;
select.appendChild(option);
}
});
}
} catch (error) {
console.error('加载卡券选项失败:', error);
}
}
// 保存发货规则
async function saveDeliveryRule() {
try {
const keyword = document.getElementById('productKeyword').value;
const cardId = document.getElementById('selectedCard').value;
const deliveryCount = document.getElementById('deliveryCount').value;
const enabled = document.getElementById('ruleEnabled').checked;
const description = document.getElementById('ruleDescription').value;
if (!keyword || !cardId) {
showToast('请填写必填字段', 'warning');
return;
}
const ruleData = {
keyword: keyword,
card_id: parseInt(cardId),
delivery_count: parseInt(deliveryCount),
enabled: enabled,
description: description
};
const response = await fetch(`${apiBase}/delivery-rules`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${authToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(ruleData)
});
if (response.ok) {
showToast('发货规则保存成功', 'success');
bootstrap.Modal.getInstance(document.getElementById('addDeliveryRuleModal')).hide();
loadDeliveryRules();
} else {
const error = await response.text();
showToast(`保存失败: ${error}`, 'danger');
}
} catch (error) {
console.error('保存发货规则失败:', error);
showToast('保存发货规则失败', 'danger');
}
}
// 编辑卡券
async function editCard(cardId) {
try {
// 获取卡券详情
const response = await fetch(`${apiBase}/cards/${cardId}`, {
headers: {
'Authorization': `Bearer ${authToken}`
}
});
if (response.ok) {
const card = await response.json();
// 填充编辑表单
document.getElementById('editCardId').value = card.id;
document.getElementById('editCardName').value = card.name;
document.getElementById('editCardType').value = card.type;
document.getElementById('editCardDescription').value = card.description || '';
document.getElementById('editCardDelaySeconds').value = card.delay_seconds || 0;
document.getElementById('editCardEnabled').checked = card.enabled;
// 填充多规格字段
const isMultiSpec = card.is_multi_spec || false;
document.getElementById('editIsMultiSpec').checked = isMultiSpec;
document.getElementById('editSpecName').value = card.spec_name || '';
document.getElementById('editSpecValue').value = card.spec_value || '';
// 添加调试日志
console.log('编辑卡券 - 多规格状态:', isMultiSpec);
console.log('编辑卡券 - 规格名称:', card.spec_name);
console.log('编辑卡券 - 规格值:', card.spec_value);
// 根据类型填充特定字段
if (card.type === 'api' && card.api_config) {
document.getElementById('editApiUrl').value = card.api_config.url || '';
document.getElementById('editApiMethod').value = card.api_config.method || 'GET';
document.getElementById('editApiTimeout').value = card.api_config.timeout || 10;
document.getElementById('editApiHeaders').value = card.api_config.headers || '{}';
document.getElementById('editApiParams').value = card.api_config.params || '{}';
} else if (card.type === 'text') {
document.getElementById('editTextContent').value = card.text_content || '';
} else if (card.type === 'data') {
document.getElementById('editDataContent').value = card.data_content || '';
}
// 显示对应的字段
toggleEditCardTypeFields();
// 使用延迟调用确保DOM更新完成后再显示多规格字段
setTimeout(() => {
console.log('延迟调用 toggleEditMultiSpecFields');
toggleEditMultiSpecFields();
// 验证多规格字段是否正确显示
const multiSpecElement = document.getElementById('editMultiSpecFields');
const isChecked = document.getElementById('editIsMultiSpec').checked;
console.log('多规格元素存在:', !!multiSpecElement);
console.log('多规格开关状态:', isChecked);
console.log('多规格字段显示状态:', multiSpecElement ? multiSpecElement.style.display : 'element not found');
}, 100);
// 显示模态框
const modal = new bootstrap.Modal(document.getElementById('editCardModal'));
modal.show();
} else {
showToast('获取卡券详情失败', 'danger');
}
} catch (error) {
console.error('获取卡券详情失败:', error);
showToast('获取卡券详情失败', 'danger');
}
}
// 切换编辑卡券类型字段显示
function toggleEditCardTypeFields() {
const cardType = document.getElementById('editCardType').value;
document.getElementById('editApiFields').style.display = cardType === 'api' ? 'block' : 'none';
document.getElementById('editTextFields').style.display = cardType === 'text' ? 'block' : 'none';
document.getElementById('editDataFields').style.display = cardType === 'data' ? 'block' : 'none';
}
// 更新卡券
async function updateCard() {
try {
const cardId = document.getElementById('editCardId').value;
const cardType = document.getElementById('editCardType').value;
const cardName = document.getElementById('editCardName').value;
if (!cardType || !cardName) {
showToast('请填写必填字段', 'warning');
return;
}
// 检查多规格设置
const isMultiSpec = document.getElementById('editIsMultiSpec').checked;
const specName = document.getElementById('editSpecName').value;
const specValue = document.getElementById('editSpecValue').value;
// 验证多规格字段
if (isMultiSpec && (!specName || !specValue)) {
showToast('多规格卡券必须填写规格名称和规格值', 'warning');
return;
}
const cardData = {
name: cardName,
type: cardType,
description: document.getElementById('editCardDescription').value,
delay_seconds: parseInt(document.getElementById('editCardDelaySeconds').value) || 0,
enabled: document.getElementById('editCardEnabled').checked,
is_multi_spec: isMultiSpec,
spec_name: isMultiSpec ? specName : null,
spec_value: isMultiSpec ? specValue : null
};
// 根据类型添加特定配置
switch(cardType) {
case 'api':
// 验证和解析JSON字段
let headers = '{}';
let params = '{}';
try {
const headersInput = document.getElementById('editApiHeaders').value.trim();
if (headersInput) {
JSON.parse(headersInput);
headers = headersInput;
}
} catch (e) {
showToast('请求头格式错误,请输入有效的JSON', 'warning');
return;
}
try {
const paramsInput = document.getElementById('editApiParams').value.trim();
if (paramsInput) {
JSON.parse(paramsInput);
params = paramsInput;
}
} catch (e) {
showToast('请求参数格式错误,请输入有效的JSON', 'warning');
return;
}
cardData.api_config = {
url: document.getElementById('editApiUrl').value,
method: document.getElementById('editApiMethod').value,
timeout: parseInt(document.getElementById('editApiTimeout').value),
headers: headers,
params: params
};
break;
case 'text':
cardData.text_content = document.getElementById('editTextContent').value;
break;
case 'data':
cardData.data_content = document.getElementById('editDataContent').value;
break;
}
const response = await fetch(`${apiBase}/cards/${cardId}`, {
method: 'PUT',
headers: {
'Authorization': `Bearer ${authToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(cardData)
});
if (response.ok) {
showToast('卡券更新成功', 'success');
bootstrap.Modal.getInstance(document.getElementById('editCardModal')).hide();
loadCards();
} else {
const error = await response.text();
showToast(`更新失败: ${error}`, 'danger');
}
} catch (error) {
console.error('更新卡券失败:', error);
showToast('更新卡券失败', 'danger');
}
}
// 测试卡券(占位函数)
function testCard(cardId) {
showToast('测试功能开发中...', 'info');
}
// 删除卡券
async function deleteCard(cardId) {
if (confirm('确定要删除这个卡券吗?删除后无法恢复!')) {
try {
const response = await fetch(`${apiBase}/cards/${cardId}`, {
method: 'DELETE',
headers: {
'Authorization': `Bearer ${authToken}`
}
});
if (response.ok) {
showToast('卡券删除成功', 'success');
loadCards();
} else {
const error = await response.text();
showToast(`删除失败: ${error}`, 'danger');
}
} catch (error) {
console.error('删除卡券失败:', error);
showToast('删除卡券失败', 'danger');
}
}
}
// 编辑发货规则
async function editDeliveryRule(ruleId) {
try {
// 获取发货规则详情
const response = await fetch(`${apiBase}/delivery-rules/${ruleId}`, {
headers: {
'Authorization': `Bearer ${authToken}`
}
});
if (response.ok) {
const rule = await response.json();
// 填充编辑表单
document.getElementById('editRuleId').value = rule.id;
document.getElementById('editProductKeyword').value = rule.keyword;
document.getElementById('editDeliveryCount').value = rule.delivery_count || 1;
document.getElementById('editRuleEnabled').checked = rule.enabled;
document.getElementById('editRuleDescription').value = rule.description || '';
// 加载卡券选项并设置当前选中的卡券
await loadCardsForEditSelect();
document.getElementById('editSelectedCard').value = rule.card_id;
// 显示模态框
const modal = new bootstrap.Modal(document.getElementById('editDeliveryRuleModal'));
modal.show();
} else {
showToast('获取发货规则详情失败', 'danger');
}
} catch (error) {
console.error('获取发货规则详情失败:', error);
showToast('获取发货规则详情失败', 'danger');
}
}
// 加载卡券列表用于编辑时的下拉选择
async function loadCardsForEditSelect() {
try {
const response = await fetch(`${apiBase}/cards`, {
headers: {
'Authorization': `Bearer ${authToken}`
}
});
if (response.ok) {
const cards = await response.json();
const select = document.getElementById('editSelectedCard');
// 清空现有选项
select.innerHTML = '';
cards.forEach(card => {
if (card.enabled) { // 只显示启用的卡券
const option = document.createElement('option');
option.value = card.id;
// 构建显示文本
let displayText = card.name;
// 添加类型信息
const typeText = card.type === 'api' ? 'API' : card.type === 'text' ? '固定文字' : '批量数据';
displayText += ` (${typeText})`;
// 添加规格信息
if (card.is_multi_spec && card.spec_name && card.spec_value) {
displayText += ` [${card.spec_name}:${card.spec_value}]`;
}
option.textContent = displayText;
select.appendChild(option);
}
});
}
} catch (error) {
console.error('加载卡券选项失败:', error);
}
}
// 更新发货规则
async function updateDeliveryRule() {
try {
const ruleId = document.getElementById('editRuleId').value;
const keyword = document.getElementById('editProductKeyword').value;
const cardId = document.getElementById('editSelectedCard').value;
const deliveryCount = document.getElementById('editDeliveryCount').value;
const enabled = document.getElementById('editRuleEnabled').checked;
const description = document.getElementById('editRuleDescription').value;
if (!keyword || !cardId) {
showToast('请填写必填字段', 'warning');
return;
}
const ruleData = {
keyword: keyword,
card_id: parseInt(cardId),
delivery_count: parseInt(deliveryCount),
enabled: enabled,
description: description
};
const response = await fetch(`${apiBase}/delivery-rules/${ruleId}`, {
method: 'PUT',
headers: {
'Authorization': `Bearer ${authToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(ruleData)
});
if (response.ok) {
showToast('发货规则更新成功', 'success');
bootstrap.Modal.getInstance(document.getElementById('editDeliveryRuleModal')).hide();
loadDeliveryRules();
} else {
const error = await response.text();
showToast(`更新失败: ${error}`, 'danger');
}
} catch (error) {
console.error('更新发货规则失败:', error);
showToast('更新发货规则失败', 'danger');
}
}
// 测试发货规则(占位函数)
function testDeliveryRule(ruleId) {
showToast('测试功能开发中...', 'info');
}
// 删除发货规则
async function deleteDeliveryRule(ruleId) {
if (confirm('确定要删除这个发货规则吗?删除后无法恢复!')) {
try {
const response = await fetch(`${apiBase}/delivery-rules/${ruleId}`, {
method: 'DELETE',
headers: {
'Authorization': `Bearer ${authToken}`
}
});
if (response.ok) {
showToast('发货规则删除成功', 'success');
loadDeliveryRules();
} else {
const error = await response.text();
showToast(`删除失败: ${error}`, 'danger');
}
} catch (error) {
console.error('删除发货规则失败:', error);
showToast('删除发货规则失败', 'danger');
}
}
}
// ==================== 系统设置功能 ====================
// 主题颜色映射
const themeColors = {
blue: '#4f46e5',
green: '#10b981',
purple: '#8b5cf6',
red: '#ef4444',
orange: '#f59e0b'
};
// 加载用户设置
async function loadUserSettings() {
try {
const response = await fetch(`${apiBase}/user-settings`, {
headers: {
'Authorization': `Bearer ${authToken}`
}
});
if (response.ok) {
const settings = await response.json();
// 设置主题颜色
if (settings.theme_color && settings.theme_color.value) {
document.getElementById('themeColor').value = settings.theme_color.value;
applyThemeColor(settings.theme_color.value);
}
}
} catch (error) {
console.error('加载用户设置失败:', error);
}
}
// 应用主题颜色
function applyThemeColor(colorName) {
const color = themeColors[colorName];
if (color) {
document.documentElement.style.setProperty('--primary-color', color);
// 计算hover颜色(稍微深一点)
const hoverColor = adjustBrightness(color, -20);
document.documentElement.style.setProperty('--primary-hover', hoverColor);
// 计算浅色版本(用于某些UI元素)
const lightColor = adjustBrightness(color, 10);
document.documentElement.style.setProperty('--primary-light', lightColor);
}
}
// 调整颜色亮度
function adjustBrightness(hex, percent) {
const num = parseInt(hex.replace("#", ""), 16);
const amt = Math.round(2.55 * percent);
const R = (num >> 16) + amt;
const G = (num >> 8 & 0x00FF) + amt;
const B = (num & 0x0000FF) + amt;
return "#" + (0x1000000 + (R < 255 ? R < 1 ? 0 : R : 255) * 0x10000 +
(G < 255 ? G < 1 ? 0 : G : 255) * 0x100 +
(B < 255 ? B < 1 ? 0 : B : 255)).toString(16).slice(1);
}
// 主题表单提交处理
document.addEventListener('DOMContentLoaded', function() {
const themeForm = document.getElementById('themeForm');
if (themeForm) {
themeForm.addEventListener('submit', async function(e) {
e.preventDefault();
const selectedColor = document.getElementById('themeColor').value;
try {
const response = await fetch(`${apiBase}/user-settings/theme_color`, {
method: 'PUT',
headers: {
'Authorization': `Bearer ${authToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
value: selectedColor,
description: '主题颜色'
})
});
if (response.ok) {
applyThemeColor(selectedColor);
showToast('主题颜色应用成功', 'success');
} else {
const error = await response.text();
showToast(`主题设置失败: ${error}`, 'danger');
}
} catch (error) {
console.error('主题设置失败:', error);
showToast('主题设置失败', 'danger');
}
});
}
// 密码表单提交处理
const passwordForm = document.getElementById('passwordForm');
if (passwordForm) {
passwordForm.addEventListener('submit', async function(e) {
e.preventDefault();
const currentPassword = document.getElementById('currentPassword').value;
const newPassword = document.getElementById('newPassword').value;
const confirmPassword = document.getElementById('confirmPassword').value;
if (newPassword !== confirmPassword) {
showToast('新密码和确认密码不匹配', 'warning');
return;
}
if (newPassword.length < 6) {
showToast('新密码长度至少6位', 'warning');
return;
}
try {
const response = await fetch(`${apiBase}/change-admin-password`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${authToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
current_password: currentPassword,
new_password: newPassword
})
});
if (response.ok) {
const result = await response.json();
if (result.success) {
showToast('密码更新成功,请重新登录', 'success');
passwordForm.reset();
// 3秒后跳转到登录页面
setTimeout(() => {
localStorage.removeItem('auth_token');
window.location.href = '/login.html';
}, 3000);
} else {
showToast(`密码更新失败: ${result.message}`, 'danger');
}
} else {
const error = await response.text();
showToast(`密码更新失败: ${error}`, 'danger');
}
} catch (error) {
console.error('密码更新失败:', error);
showToast('密码更新失败', 'danger');
}
});
}
// 页面加载时加载用户设置
loadUserSettings();
});
// ==================== 备份管理功能 ====================
// 下载数据库备份
async function downloadDatabaseBackup() {
try {
showToast('正在准备数据库备份,请稍候...', 'info');
const response = await fetch(`${apiBase}/admin/backup/download`, {
headers: {
'Authorization': `Bearer ${authToken}`
}
});
if (response.ok) {
// 获取文件名
const contentDisposition = response.headers.get('content-disposition');
let filename = 'xianyu_backup.db';
if (contentDisposition) {
const filenameMatch = contentDisposition.match(/filename="(.+)"/);
if (filenameMatch) {
filename = filenameMatch[1];
}
}
// 下载文件
const blob = await response.blob();
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);
showToast('数据库备份下载成功', 'success');
} else {
const error = await response.text();
showToast(`下载失败: ${error}`, 'danger');
}
} catch (error) {
console.error('下载数据库备份失败:', error);
showToast('下载数据库备份失败', 'danger');
}
}
// 上传数据库备份
async function uploadDatabaseBackup() {
const fileInput = document.getElementById('databaseFile');
const file = fileInput.files[0];
if (!file) {
showToast('请选择数据库文件', 'warning');
return;
}
if (!file.name.endsWith('.db')) {
showToast('只支持.db格式的数据库文件', 'warning');
return;
}
// 文件大小检查(限制100MB)
if (file.size > 100 * 1024 * 1024) {
showToast('数据库文件大小不能超过100MB', 'warning');
return;
}
if (!confirm('恢复数据库将完全替换当前所有数据,包括所有用户、Cookie、卡券等信息。\n\n此操作不可撤销!\n\n确定要继续吗?')) {
return;
}
try {
showToast('正在上传并恢复数据库,请稍候...', 'info');
const formData = new FormData();
formData.append('backup_file', file);
const response = await fetch(`${apiBase}/admin/backup/upload`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${authToken}`
},
body: formData
});
if (response.ok) {
const result = await response.json();
showToast(`数据库恢复成功!包含 ${result.user_count} 个用户`, 'success');
// 清空文件选择
fileInput.value = '';
// 提示用户刷新页面
setTimeout(() => {
if (confirm('数据库已恢复,建议刷新页面以加载新数据。是否立即刷新?')) {
window.location.reload();
}
}, 2000);
} else {
const error = await response.json();
showToast(`恢复失败: ${error.detail}`, 'danger');
}
} catch (error) {
console.error('上传数据库备份失败:', error);
showToast('上传数据库备份失败', 'danger');
}
}
// 导出备份(JSON格式,兼容旧版本)
async function exportBackup() {
try {
showToast('正在导出备份,请稍候...', 'info');
const response = await fetch(`${apiBase}/backup/export`, {
headers: {
'Authorization': `Bearer ${authToken}`
}
});
if (response.ok) {
const backupData = await response.json();
// 生成文件名
const now = new Date();
const timestamp = now.getFullYear() +
String(now.getMonth() + 1).padStart(2, '0') +
String(now.getDate()).padStart(2, '0') + '_' +
String(now.getHours()).padStart(2, '0') +
String(now.getMinutes()).padStart(2, '0') +
String(now.getSeconds()).padStart(2, '0');
const filename = `xianyu_backup_${timestamp}.json`;
// 创建下载链接
const blob = new Blob([JSON.stringify(backupData, null, 2)], { type: 'application/json' });
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);
showToast('备份导出成功', 'success');
} else {
const error = await response.text();
showToast(`导出失败: ${error}`, 'danger');
}
} catch (error) {
console.error('导出备份失败:', error);
showToast('导出备份失败', 'danger');
}
}
// 导入备份
async function importBackup() {
const fileInput = document.getElementById('backupFile');
const file = fileInput.files[0];
if (!file) {
showToast('请选择备份文件', 'warning');
return;
}
if (!file.name.endsWith('.json')) {
showToast('只支持JSON格式的备份文件', 'warning');
return;
}
if (!confirm('导入备份将覆盖当前所有数据,确定要继续吗?')) {
return;
}
try {
showToast('正在导入备份,请稍候...', 'info');
const formData = new FormData();
formData.append('file', file);
const response = await fetch(`${apiBase}/backup/import`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${authToken}`
},
body: formData
});
if (response.ok) {
showToast('备份导入成功!正在刷新数据...', 'success');
// 清空文件选择
fileInput.value = '';
// 清除前端缓存
clearKeywordCache();
// 延迟一下再刷新数据,确保后端缓存已更新
setTimeout(async () => {
try {
// 如果当前在关键字管理页面,重新加载数据
if (currentCookieId) {
await loadAccountKeywords();
}
// 刷新仪表盘数据
if (document.getElementById('dashboard-section').classList.contains('active')) {
await loadDashboard();
}
// 刷新账号列表
if (document.getElementById('accounts-section').classList.contains('active')) {
await loadCookies();
}
showToast('数据刷新完成!', 'success');
} catch (error) {
console.error('刷新数据失败:', error);
showToast('备份导入成功,但数据刷新失败,请手动刷新页面', 'warning');
}
}, 1000);
} else {
const error = await response.text();
showToast(`导入失败: ${error}`, 'danger');
}
} catch (error) {
console.error('导入备份失败:', error);
showToast('导入备份失败', 'danger');
}
}
// 刷新系统缓存
async function reloadSystemCache() {
try {
showToast('正在刷新系统缓存...', 'info');
const response = await fetch(`${apiBase}/system/reload-cache`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${authToken}`
}
});
if (response.ok) {
const result = await response.json();
showToast('系统缓存刷新成功!关键字等数据已更新', 'success');
// 清除前端缓存
clearKeywordCache();
// 如果当前在关键字管理页面,重新加载数据
if (currentCookieId) {
setTimeout(() => {
loadAccountKeywords();
}, 500);
}
} else {
const error = await response.text();
showToast(`刷新缓存失败: ${error}`, 'danger');
}
} catch (error) {
console.error('刷新系统缓存失败:', error);
showToast('刷新系统缓存失败', 'danger');
}
}
// ==================== 商品管理功能 ====================
// 切换商品多规格状态
async function toggleItemMultiSpec(cookieId, itemId, isMultiSpec) {
try {
const response = await fetch(`${apiBase}/items/${encodeURIComponent(cookieId)}/${encodeURIComponent(itemId)}/multi-spec`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${authToken}`
},
body: JSON.stringify({
is_multi_spec: isMultiSpec
})
});
if (response.ok) {
showToast(`${isMultiSpec ? '开启' : '关闭'}多规格成功`, 'success');
// 刷新商品列表
await refreshItemsData();
} else {
const errorData = await response.json();
throw new Error(errorData.error || '操作失败');
}
} catch (error) {
console.error('切换多规格状态失败:', error);
showToast(`切换多规格状态失败: ${error.message}`, 'danger');
}
}
// 加载商品列表
async function loadItems() {
try {
// 先加载Cookie列表用于筛选
await loadCookieFilter();
// 加载商品列表
await refreshItemsData();
} catch (error) {
console.error('加载商品列表失败:', error);
showToast('加载商品列表失败', 'danger');
}
}
// 只刷新商品数据,不重新加载筛选器
async function refreshItemsData() {
try {
const selectedCookie = document.getElementById('itemCookieFilter').value;
if (selectedCookie) {
await loadItemsByCookie();
} else {
await loadAllItems();
}
} catch (error) {
console.error('刷新商品数据失败:', error);
showToast('刷新商品数据失败', 'danger');
}
}
// 加载Cookie筛选选项
async function loadCookieFilter() {
try {
const response = await fetch(`${apiBase}/cookies/details`, {
headers: {
'Authorization': `Bearer ${authToken}`
}
});
if (response.ok) {
const accounts = await response.json();
const select = document.getElementById('itemCookieFilter');
// 保存当前选择的值
const currentValue = select.value;
// 清空现有选项(保留"所有账号")
select.innerHTML = '';
if (accounts.length === 0) {
const option = document.createElement('option');
option.value = '';
option.textContent = '❌ 暂无账号';
option.disabled = true;
select.appendChild(option);
return;
}
// 分组显示:先显示启用的账号,再显示禁用的账号
const enabledAccounts = accounts.filter(account => {
const enabled = account.enabled === undefined ? true : account.enabled;
return enabled;
});
const disabledAccounts = accounts.filter(account => {
const enabled = account.enabled === undefined ? true : account.enabled;
return !enabled;
});
// 添加启用的账号
enabledAccounts.forEach(account => {
const option = document.createElement('option');
option.value = account.id;
option.textContent = `🟢 ${account.id}`;
select.appendChild(option);
});
// 添加禁用的账号
if (disabledAccounts.length > 0) {
// 添加分隔线
if (enabledAccounts.length > 0) {
const separator = document.createElement('option');
separator.value = '';
separator.textContent = '────────────────';
separator.disabled = true;
select.appendChild(separator);
}
disabledAccounts.forEach(account => {
const option = document.createElement('option');
option.value = account.id;
option.textContent = `🔴 ${account.id} (已禁用)`;
select.appendChild(option);
});
}
// 恢复之前选择的值
if (currentValue) {
select.value = currentValue;
}
}
} catch (error) {
console.error('加载Cookie列表失败:', error);
showToast('加载账号列表失败', 'danger');
}
}
// 加载所有商品
async function loadAllItems() {
try {
const response = await fetch(`${apiBase}/items`, {
headers: {
'Authorization': `Bearer ${authToken}`
}
});
if (response.ok) {
const data = await response.json();
displayItems(data.items);
} else {
throw new Error('获取商品列表失败');
}
} catch (error) {
console.error('加载商品列表失败:', error);
showToast('加载商品列表失败', 'danger');
}
}
// 按Cookie加载商品
async function loadItemsByCookie() {
const cookieId = document.getElementById('itemCookieFilter').value;
if (!cookieId) {
await loadAllItems();
return;
}
try {
const response = await fetch(`${apiBase}/items/cookie/${encodeURIComponent(cookieId)}`, {
headers: {
'Authorization': `Bearer ${authToken}`
}
});
if (response.ok) {
const data = await response.json();
displayItems(data.items);
} else {
throw new Error('获取商品列表失败');
}
} catch (error) {
console.error('加载商品列表失败:', error);
showToast('加载商品列表失败', 'danger');
}
}
// 显示商品列表
function displayItems(items) {
const tbody = document.getElementById('itemsTableBody');
if (!items || items.length === 0) {
tbody.innerHTML = '暂无商品数据 |
';
// 重置选择状态
const selectAllCheckbox = document.getElementById('selectAllItems');
if (selectAllCheckbox) {
selectAllCheckbox.checked = false;
selectAllCheckbox.indeterminate = false;
}
updateBatchDeleteButton();
return;
}
const itemsHtml = items.map(item => {
// 处理商品标题显示
let itemTitleDisplay = item.item_title || '未设置';
if (itemTitleDisplay.length > 30) {
itemTitleDisplay = itemTitleDisplay.substring(0, 30) + '...';
}
// 处理商品详情显示
let itemDetailDisplay = '未设置';
if (item.item_detail) {
try {
// 尝试解析JSON并提取有用信息
const detail = JSON.parse(item.item_detail);
if (detail.content) {
itemDetailDisplay = detail.content.substring(0, 50) + (detail.content.length > 50 ? '...' : '');
} else {
// 如果是纯文本或其他格式,直接显示前50个字符
itemDetailDisplay = item.item_detail.substring(0, 50) + (item.item_detail.length > 50 ? '...' : '');
}
} catch (e) {
// 如果不是JSON格式,直接显示前50个字符
itemDetailDisplay = item.item_detail.substring(0, 50) + (item.item_detail.length > 50 ? '...' : '');
}
}
// 多规格状态显示
const isMultiSpec = item.is_multi_spec;
const multiSpecDisplay = isMultiSpec ?
'多规格' :
'普通';
return `
|
${escapeHtml(item.cookie_id)} |
${escapeHtml(item.item_id)} |
${escapeHtml(itemTitleDisplay)} |
${escapeHtml(itemDetailDisplay)} |
${multiSpecDisplay} |
${formatDateTime(item.updated_at)} |
|
`;
}).join('');
// 更新表格内容
tbody.innerHTML = itemsHtml;
// 重置选择状态
const selectAllCheckbox = document.getElementById('selectAllItems');
if (selectAllCheckbox) {
selectAllCheckbox.checked = false;
selectAllCheckbox.indeterminate = false;
}
updateBatchDeleteButton();
}
// 刷新商品列表
async function refreshItems() {
await refreshItemsData();
showToast('商品列表已刷新', 'success');
}
// 获取商品信息
async function getAllItemsFromAccount() {
const cookieSelect = document.getElementById('itemCookieFilter');
const selectedCookieId = cookieSelect.value;
const pageNumber = parseInt(document.getElementById('pageNumber').value) || 1;
if (!selectedCookieId) {
showToast('请先选择一个账号', 'warning');
return;
}
if (pageNumber < 1) {
showToast('页码必须大于0', 'warning');
return;
}
// 显示加载状态
const button = event.target;
const originalText = button.innerHTML;
button.innerHTML = '获取中...';
button.disabled = true;
try {
const response = await fetch(`${apiBase}/items/get-by-page`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${authToken}`
},
body: JSON.stringify({
cookie_id: selectedCookieId,
page_number: pageNumber,
page_size: 20
})
});
if (response.ok) {
const data = await response.json();
if (data.success) {
showToast(`成功获取第${pageNumber}页 ${data.current_count} 个商品,请查看控制台日志`, 'success');
// 刷新商品列表(保持筛选器选择)
await refreshItemsData();
} else {
showToast(data.message || '获取商品信息失败', 'danger');
}
} else {
throw new Error(`HTTP ${response.status}`);
}
} catch (error) {
console.error('获取商品信息失败:', error);
showToast('获取商品信息失败', 'danger');
} finally {
// 恢复按钮状态
button.innerHTML = originalText;
button.disabled = false;
}
}
// 获取所有页商品信息
async function getAllItemsFromAccountAll() {
const cookieSelect = document.getElementById('itemCookieFilter');
const selectedCookieId = cookieSelect.value;
if (!selectedCookieId) {
showToast('请先选择一个账号', 'warning');
return;
}
// 显示加载状态
const button = event.target;
const originalText = button.innerHTML;
button.innerHTML = '获取中...';
button.disabled = true;
try {
const response = await fetch(`${apiBase}/items/get-all-from-account`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${authToken}`
},
body: JSON.stringify({
cookie_id: selectedCookieId
})
});
if (response.ok) {
const data = await response.json();
if (data.success) {
const message = data.total_pages ?
`成功获取 ${data.total_count} 个商品(共${data.total_pages}页),请查看控制台日志` :
`成功获取商品信息,请查看控制台日志`;
showToast(message, 'success');
// 刷新商品列表(保持筛选器选择)
await refreshItemsData();
} else {
showToast(data.message || '获取商品信息失败', 'danger');
}
} else {
throw new Error(`HTTP ${response.status}`);
}
} catch (error) {
console.error('获取商品信息失败:', error);
showToast('获取商品信息失败', 'danger');
} finally {
// 恢复按钮状态
button.innerHTML = originalText;
button.disabled = false;
}
}
// 编辑商品详情
async function editItem(cookieId, itemId) {
try {
const response = await fetch(`${apiBase}/items/${encodeURIComponent(cookieId)}/${encodeURIComponent(itemId)}`, {
headers: {
'Authorization': `Bearer ${authToken}`
}
});
if (response.ok) {
const data = await response.json();
const item = data.item;
// 填充表单
document.getElementById('editItemCookieId').value = item.cookie_id;
document.getElementById('editItemId').value = item.item_id;
document.getElementById('editItemCookieIdDisplay').value = item.cookie_id;
document.getElementById('editItemIdDisplay').value = item.item_id;
document.getElementById('editItemDetail').value = item.item_detail || '';
// 显示模态框
const modal = new bootstrap.Modal(document.getElementById('editItemModal'));
modal.show();
} else {
throw new Error('获取商品详情失败');
}
} catch (error) {
console.error('获取商品详情失败:', error);
showToast('获取商品详情失败', 'danger');
}
}
// 保存商品详情
async function saveItemDetail() {
const cookieId = document.getElementById('editItemCookieId').value;
const itemId = document.getElementById('editItemId').value;
const itemDetail = document.getElementById('editItemDetail').value.trim();
if (!itemDetail) {
showToast('请输入商品详情', 'warning');
return;
}
try {
const response = await fetch(`${apiBase}/items/${encodeURIComponent(cookieId)}/${encodeURIComponent(itemId)}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${authToken}`
},
body: JSON.stringify({
item_detail: itemDetail
})
});
if (response.ok) {
showToast('商品详情更新成功', 'success');
// 关闭模态框
const modal = bootstrap.Modal.getInstance(document.getElementById('editItemModal'));
modal.hide();
// 刷新列表(保持筛选器选择)
await refreshItemsData();
} else {
const error = await response.text();
showToast(`更新失败: ${error}`, 'danger');
}
} catch (error) {
console.error('更新商品详情失败:', error);
showToast('更新商品详情失败', 'danger');
}
}
// 删除商品信息
async function deleteItem(cookieId, itemId, itemTitle) {
try {
// 确认删除
const confirmed = confirm(`确定要删除商品信息吗?\n\n商品ID: ${itemId}\n商品标题: ${itemTitle || '未设置'}\n\n此操作不可撤销!`);
if (!confirmed) {
return;
}
const response = await fetch(`${apiBase}/items/${encodeURIComponent(cookieId)}/${encodeURIComponent(itemId)}`, {
method: 'DELETE',
headers: {
'Authorization': `Bearer ${authToken}`
}
});
if (response.ok) {
showToast('商品信息删除成功', 'success');
// 刷新列表(保持筛选器选择)
await refreshItemsData();
} else {
const error = await response.text();
showToast(`删除失败: ${error}`, 'danger');
}
} catch (error) {
console.error('删除商品信息失败:', error);
showToast('删除商品信息失败', 'danger');
}
}
// 批量删除商品信息
async function batchDeleteItems() {
try {
// 获取所有选中的复选框
const checkboxes = document.querySelectorAll('input[name="itemCheckbox"]:checked');
if (checkboxes.length === 0) {
showToast('请选择要删除的商品', 'warning');
return;
}
// 确认删除
const confirmed = confirm(`确定要删除选中的 ${checkboxes.length} 个商品信息吗?\n\n此操作不可撤销!`);
if (!confirmed) {
return;
}
// 构造删除列表
const itemsToDelete = Array.from(checkboxes).map(checkbox => {
const row = checkbox.closest('tr');
return {
cookie_id: checkbox.dataset.cookieId,
item_id: checkbox.dataset.itemId
};
});
const response = await fetch(`${apiBase}/items/batch`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${authToken}`
},
body: JSON.stringify({ items: itemsToDelete })
});
if (response.ok) {
const result = await response.json();
showToast(`批量删除完成: 成功 ${result.success_count} 个,失败 ${result.failed_count} 个`, 'success');
// 刷新列表(保持筛选器选择)
await refreshItemsData();
} else {
const error = await response.text();
showToast(`批量删除失败: ${error}`, 'danger');
}
} catch (error) {
console.error('批量删除商品信息失败:', error);
showToast('批量删除商品信息失败', 'danger');
}
}
// 全选/取消全选
function toggleSelectAll(selectAllCheckbox) {
const checkboxes = document.querySelectorAll('input[name="itemCheckbox"]');
checkboxes.forEach(checkbox => {
checkbox.checked = selectAllCheckbox.checked;
});
updateBatchDeleteButton();
}
// 更新全选状态
function updateSelectAllState() {
const checkboxes = document.querySelectorAll('input[name="itemCheckbox"]');
const checkedCheckboxes = document.querySelectorAll('input[name="itemCheckbox"]:checked');
const selectAllCheckbox = document.getElementById('selectAllItems');
if (checkboxes.length === 0) {
selectAllCheckbox.checked = false;
selectAllCheckbox.indeterminate = false;
} else if (checkedCheckboxes.length === checkboxes.length) {
selectAllCheckbox.checked = true;
selectAllCheckbox.indeterminate = false;
} else if (checkedCheckboxes.length > 0) {
selectAllCheckbox.checked = false;
selectAllCheckbox.indeterminate = true;
} else {
selectAllCheckbox.checked = false;
selectAllCheckbox.indeterminate = false;
}
updateBatchDeleteButton();
}
// 更新批量删除按钮状态
function updateBatchDeleteButton() {
const checkedCheckboxes = document.querySelectorAll('input[name="itemCheckbox"]:checked');
const batchDeleteBtn = document.getElementById('batchDeleteBtn');
if (checkedCheckboxes.length > 0) {
batchDeleteBtn.disabled = false;
batchDeleteBtn.innerHTML = ` 批量删除 (${checkedCheckboxes.length})`;
} else {
batchDeleteBtn.disabled = true;
batchDeleteBtn.innerHTML = ' 批量删除';
}
}
// 格式化日期时间
function formatDateTime(dateString) {
if (!dateString) return '未知';
const date = new Date(dateString);
return date.toLocaleString('zh-CN');
}
// HTML转义函数
function escapeHtml(text) {
if (!text) return '';
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
// ==================== 日志管理功能 ====================
window.autoRefreshInterval = null;
window.allLogs = [];
window.filteredLogs = [];
// 刷新日志
async function refreshLogs() {
try {
const lines = document.getElementById('logLines').value;
const response = await fetch(`${apiBase}/logs?lines=${lines}`, {
headers: {
'Authorization': `Bearer ${authToken}`
}
});
if (response.ok) {
const data = await response.json();
window.allLogs = data.logs || [];
window.filteredLogs = window.allLogs; // 不再过滤,直接显示所有日志
displayLogs();
updateLogStats();
showToast('日志已刷新', 'success');
} else {
throw new Error(`HTTP ${response.status}`);
}
} catch (error) {
console.error('刷新日志失败:', error);
showToast('刷新日志失败', 'danger');
}
}
// 显示日志
function displayLogs() {
const container = document.getElementById('logContainer');
if (window.filteredLogs.length === 0) {
container.innerHTML = `
`;
return;
}
const logsHtml = window.filteredLogs.map(log => {
const timestamp = formatLogTimestamp(log.timestamp);
const levelClass = log.level || 'INFO';
return `
${timestamp}
[${log.level}]
${log.source}:
${escapeHtml(log.message)}
`;
}).join('');
container.innerHTML = logsHtml;
// 滚动到底部
container.scrollTop = container.scrollHeight;
}
// 格式化日志时间戳
function formatLogTimestamp(timestamp) {
if (!timestamp) return '';
const date = new Date(timestamp);
return date.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
fractionalSecondDigits: 3
});
}
// 更新日志统计信息
function updateLogStats() {
document.getElementById('logCount').textContent = `${window.filteredLogs.length} 条日志`;
document.getElementById('lastUpdate').textContent = new Date().toLocaleTimeString('zh-CN');
}
// 清空日志显示
function clearLogsDisplay() {
window.allLogs = [];
window.filteredLogs = [];
document.getElementById('logContainer').innerHTML = `
`;
updateLogStats();
showToast('日志显示已清空', 'info');
}
// 切换自动刷新
function toggleAutoRefresh() {
const button = document.querySelector('#autoRefreshText');
const icon = button.previousElementSibling;
if (window.autoRefreshInterval) {
// 停止自动刷新
clearInterval(window.autoRefreshInterval);
window.autoRefreshInterval = null;
button.textContent = '开启自动刷新';
icon.className = 'bi bi-play-circle me-1';
showToast('自动刷新已停止', 'info');
} else {
// 开启自动刷新
window.autoRefreshInterval = setInterval(refreshLogs, 5000); // 每5秒刷新一次
button.textContent = '停止自动刷新';
icon.className = 'bi bi-pause-circle me-1';
showToast('自动刷新已开启(每5秒)', 'success');
// 立即刷新一次
refreshLogs();
}
}
// 清空服务器日志
async function clearLogsServer() {
if (!confirm('确定要清空服务器端的所有日志吗?此操作不可恢复!')) {
return;
}
try {
const response = await fetch(`${apiBase}/logs/clear`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${authToken}`
}
});
if (response.ok) {
const data = await response.json();
if (data.success) {
window.allLogs = [];
window.filteredLogs = [];
displayLogs();
updateLogStats();
showToast('服务器日志已清空', 'success');
} else {
showToast(data.message || '清空失败', 'danger');
}
} else {
throw new Error(`HTTP ${response.status}`);
}
} catch (error) {
console.error('清空服务器日志失败:', error);
showToast('清空服务器日志失败', 'danger');
}
}
// 显示日志统计信息
async function showLogStats() {
try {
const response = await fetch(`${apiBase}/logs/stats`, {
headers: {
'Authorization': `Bearer ${authToken}`
}
});
if (response.ok) {
const data = await response.json();
if (data.success) {
const stats = data.stats;
let statsHtml = `
总体统计
- 总日志数: ${stats.total_logs}
- 最大容量: ${stats.max_capacity}
- 使用率: ${((stats.total_logs / stats.max_capacity) * 100).toFixed(1)}%
级别分布
`;
for (const [level, count] of Object.entries(stats.level_counts || {})) {
const percentage = ((count / stats.total_logs) * 100).toFixed(1);
statsHtml += `- ${level}: ${count} (${percentage}%)
`;
}
statsHtml += `
来源分布
`;
const sources = Object.entries(stats.source_counts || {});
sources.forEach(([source, count], index) => {
if (index % 2 === 0) statsHtml += '
';
const percentage = ((count / stats.total_logs) * 100).toFixed(1);
statsHtml += `- ${source}: ${count} (${percentage}%)
`;
if (index % 2 === 1 || index === sources.length - 1) statsHtml += '
';
});
statsHtml += `
`;
// 显示模态框
const modalHtml = `
`;
// 移除旧的模态框
const oldModal = document.getElementById('logStatsModal');
if (oldModal) oldModal.remove();
// 添加新的模态框
document.body.insertAdjacentHTML('beforeend', modalHtml);
// 显示模态框
const modal = new bootstrap.Modal(document.getElementById('logStatsModal'));
modal.show();
} else {
showToast(data.message || '获取统计信息失败', 'danger');
}
} else {
throw new Error(`HTTP ${response.status}`);
}
} catch (error) {
console.error('获取日志统计失败:', error);
showToast('获取日志统计失败', 'danger');
}
}
// ==================== 导入导出功能 ====================
// 导出关键词
async function exportKeywords() {
if (!currentCookieId) {
showToast('请先选择账号', 'warning');
return;
}
try {
const response = await fetch(`${apiBase}/keywords-export/${currentCookieId}`, {
headers: {
'Authorization': `Bearer ${authToken}`
}
});
if (response.ok) {
// 创建下载链接
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
// 根据当前账号是否有数据来设置文件名和提示
const currentKeywords = keywordsData[currentCookieId] || [];
const hasData = currentKeywords.length > 0;
if (hasData) {
a.download = `keywords_${currentCookieId}_${new Date().getTime()}.xlsx`;
showToast('关键词导出成功!', 'success');
} else {
a.download = `keywords_template_${currentCookieId}_${new Date().getTime()}.xlsx`;
showToast('导入模板导出成功!模板中包含示例数据供参考', 'success');
}
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
} else {
const error = await response.json();
showToast(`导出失败: ${error.detail}`, 'error');
}
} catch (error) {
console.error('导出关键词失败:', error);
showToast('导出关键词失败', 'error');
}
}
// 显示导入模态框
function showImportModal() {
if (!currentCookieId) {
showToast('请先选择账号', 'warning');
return;
}
const modal = new bootstrap.Modal(document.getElementById('importKeywordsModal'));
modal.show();
}
// 导入关键词
async function importKeywords() {
if (!currentCookieId) {
showToast('请先选择账号', 'warning');
return;
}
const fileInput = document.getElementById('importFileInput');
const file = fileInput.files[0];
if (!file) {
showToast('请选择要导入的Excel文件', 'warning');
return;
}
try {
// 显示进度条
const progressDiv = document.getElementById('importProgress');
const progressBar = progressDiv.querySelector('.progress-bar');
progressDiv.style.display = 'block';
progressBar.style.width = '30%';
const formData = new FormData();
formData.append('file', file);
const response = await fetch(`${apiBase}/keywords-import/${currentCookieId}`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${authToken}`
},
body: formData
});
progressBar.style.width = '70%';
if (response.ok) {
const result = await response.json();
progressBar.style.width = '100%';
setTimeout(() => {
progressDiv.style.display = 'none';
progressBar.style.width = '0%';
// 关闭模态框
const modal = bootstrap.Modal.getInstance(document.getElementById('importKeywordsModal'));
modal.hide();
// 清空文件输入
fileInput.value = '';
// 重新加载关键词列表
loadAccountKeywords(currentCookieId);
showToast(`导入成功!新增: ${result.added}, 更新: ${result.updated}`, 'success');
}, 500);
} else {
const error = await response.json();
progressDiv.style.display = 'none';
progressBar.style.width = '0%';
showToast(`导入失败: ${error.detail}`, 'error');
}
} catch (error) {
console.error('导入关键词失败:', error);
document.getElementById('importProgress').style.display = 'none';
document.querySelector('#importProgress .progress-bar').style.width = '0%';
showToast('导入关键词失败', 'error');
}
}
// ========================= 账号添加相关函数 =========================
// 切换手动输入表单显示/隐藏
function toggleManualInput() {
const manualForm = document.getElementById('manualInputForm');
if (manualForm.style.display === 'none') {
manualForm.style.display = 'block';
// 清空表单
document.getElementById('addForm').reset();
} else {
manualForm.style.display = 'none';
}
}
// ========================= 扫码登录相关函数 =========================
let qrCodeCheckInterval = null;
let qrCodeSessionId = null;
// 显示扫码登录模态框
function showQRCodeLogin() {
const modal = new bootstrap.Modal(document.getElementById('qrCodeLoginModal'));
modal.show();
// 模态框显示后生成二维码
modal._element.addEventListener('shown.bs.modal', function () {
generateQRCode();
});
// 模态框关闭时清理定时器
modal._element.addEventListener('hidden.bs.modal', function () {
clearQRCodeCheck();
});
}
// 刷新二维码(兼容旧函数名)
async function refreshQRCode() {
await generateQRCode();
}
// 生成二维码
async function generateQRCode() {
try {
showQRCodeLoading();
const response = await fetch(`${apiBase}/qr-login/generate`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${authToken}`,
'Content-Type': 'application/json'
}
});
if (response.ok) {
const data = await response.json();
if (data.success) {
qrCodeSessionId = data.session_id;
showQRCodeImage(data.qr_code_url);
startQRCodeCheck();
} else {
showQRCodeError(data.message || '生成二维码失败');
}
} else {
showQRCodeError('生成二维码失败');
}
} catch (error) {
console.error('生成二维码失败:', error);
showQRCodeError('网络错误,请重试');
}
}
// 显示二维码加载状态
function showQRCodeLoading() {
document.getElementById('qrCodeContainer').style.display = 'block';
document.getElementById('qrCodeImage').style.display = 'none';
document.getElementById('statusText').textContent = '正在生成二维码,请耐心等待...';
document.getElementById('statusSpinner').style.display = 'none';
// 隐藏验证容器
const verificationContainer = document.getElementById('verificationContainer');
if (verificationContainer) {
verificationContainer.style.display = 'none';
}
}
// 显示二维码图片
function showQRCodeImage(qrCodeUrl) {
document.getElementById('qrCodeContainer').style.display = 'none';
document.getElementById('qrCodeImage').style.display = 'block';
document.getElementById('qrCodeImg').src = qrCodeUrl;
document.getElementById('statusText').textContent = '等待扫码...';
document.getElementById('statusSpinner').style.display = 'none';
}
// 显示二维码错误
function showQRCodeError(message) {
document.getElementById('qrCodeContainer').innerHTML = `
`;
document.getElementById('qrCodeImage').style.display = 'none';
document.getElementById('statusText').textContent = '生成失败';
document.getElementById('statusSpinner').style.display = 'none';
}
// 开始检查二维码状态
function startQRCodeCheck() {
if (qrCodeCheckInterval) {
clearInterval(qrCodeCheckInterval);
}
document.getElementById('statusSpinner').style.display = 'inline-block';
document.getElementById('statusText').textContent = '等待扫码...';
qrCodeCheckInterval = setInterval(checkQRCodeStatus, 2000); // 每2秒检查一次
}
// 检查二维码状态
async function checkQRCodeStatus() {
if (!qrCodeSessionId) return;
try {
const response = await fetch(`${apiBase}/qr-login/check/${qrCodeSessionId}`, {
headers: {
'Authorization': `Bearer ${authToken}`
}
});
if (response.ok) {
const data = await response.json();
switch (data.status) {
case 'waiting':
document.getElementById('statusText').textContent = '等待扫码...';
break;
case 'scanned':
document.getElementById('statusText').textContent = '已扫码,请在手机上确认...';
break;
case 'success':
document.getElementById('statusText').textContent = '登录成功!';
document.getElementById('statusSpinner').style.display = 'none';
clearQRCodeCheck();
handleQRCodeSuccess(data);
break;
case 'expired':
document.getElementById('statusText').textContent = '二维码已过期';
document.getElementById('statusSpinner').style.display = 'none';
clearQRCodeCheck();
showQRCodeError('二维码已过期,请刷新重试');
break;
case 'cancelled':
document.getElementById('statusText').textContent = '用户取消登录';
document.getElementById('statusSpinner').style.display = 'none';
clearQRCodeCheck();
break;
case 'verification_required':
document.getElementById('statusText').textContent = '需要手机验证';
document.getElementById('statusSpinner').style.display = 'none';
clearQRCodeCheck();
showVerificationRequired(data);
break;
}
}
} catch (error) {
console.error('检查二维码状态失败:', error);
}
}
// 显示需要验证的提示
function showVerificationRequired(data) {
if (data.verification_url) {
// 隐藏二维码区域
document.getElementById('qrCodeContainer').style.display = 'none';
document.getElementById('qrCodeImage').style.display = 'none';
// 显示验证提示
const verificationHtml = `
账号需要手机验证
检测到账号存在风控,需要进行手机验证才能完成登录
验证步骤:
1. 点击上方按钮打开验证页面
2. 按照页面提示完成手机验证
3. 验证完成后,重新扫码登录
`;
// 创建验证提示容器
let verificationContainer = document.getElementById('verificationContainer');
if (!verificationContainer) {
verificationContainer = document.createElement('div');
verificationContainer.id = 'verificationContainer';
document.querySelector('#qrCodeLoginModal .modal-body').appendChild(verificationContainer);
}
verificationContainer.innerHTML = verificationHtml;
verificationContainer.style.display = 'block';
// 显示Toast提示
showToast('账号需要手机验证,请按照提示完成验证', 'warning');
}
}
// 处理扫码成功
function handleQRCodeSuccess(data) {
if (data.account_info) {
const { account_id, is_new_account } = data.account_info;
if (is_new_account) {
showToast(`新账号添加成功!账号ID: ${account_id}`, 'success');
} else {
showToast(`账号Cookie已更新!账号ID: ${account_id}`, 'success');
}
// 关闭模态框
setTimeout(() => {
const modal = bootstrap.Modal.getInstance(document.getElementById('qrCodeLoginModal'));
modal.hide();
// 刷新账号列表
loadCookies();
}, 2000);
}
}
// 清理二维码检查
function clearQRCodeCheck() {
if (qrCodeCheckInterval) {
clearInterval(qrCodeCheckInterval);
qrCodeCheckInterval = null;
}
qrCodeSessionId = null;
}
// 刷新二维码
function refreshQRCode() {
clearQRCodeCheck();
generateQRCode();
}