// ==================== 全局变量 ==================== 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 = `
${item.keyword} ${itemIdDisplay}

${item.reply}

`; 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 = `
${message}
`; 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.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 = `
总体统计
级别分布
来源分布
`; 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 = `

${message}

`; 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(); }