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