diff --git a/static/js/app.js b/static/js/app.js
deleted file mode 100644
index 5a56449..0000000
--- a/static/js/app.js
+++ /dev/null
@@ -1,5519 +0,0 @@
-// 全局变量
-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();
- }
\ No newline at end of file