From 04a391616ffbb856e393dc8ba04b487c768e13d6 Mon Sep 17 00:00:00 2001
From: zhinianboke <115088296+zhinianboke@users.noreply.github.com>
Date: Sun, 3 Aug 2025 20:55:31 +0800
Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E7=BB=93=E6=9E=84?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
static/js/app.js | 1258 +++++++++++++++++++++++-----------------------
1 file changed, 641 insertions(+), 617 deletions(-)
diff --git a/static/js/app.js b/static/js/app.js
index 5a56449..9f1b806 100644
--- a/static/js/app.js
+++ b/static/js/app.js
@@ -1,12 +1,12 @@
-// 全局变量
+// ==================== 全局变量 ====================
const apiBase = location.origin;
let keywordsData = {};
let currentCookieId = '';
let editCookieId = '';
let authToken = localStorage.getItem('auth_token');
let dashboardData = {
- accounts: [],
- totalKeywords: 0
+ accounts: [],
+ totalKeywords: 0
};
// 账号关键词缓存
@@ -14,401 +14,409 @@ let accountKeywordCache = {};
let cacheTimestamp = 0;
const CACHE_DURATION = 30000; // 30秒缓存
+// ==================== 页面导航功能 ====================
+
// 菜单切换功能
function showSection(sectionName) {
- console.log('切换到页面:', sectionName); // 调试信息
+ console.log('切换到页面:', sectionName); // 调试信息
- // 隐藏所有内容区域
- document.querySelectorAll('.content-section').forEach(section => {
- section.classList.remove('active');
- });
+ // 隐藏所有内容区域
+ document.querySelectorAll('.content-section').forEach(section => {
+ section.classList.remove('active');
+ });
- // 移除所有菜单项的active状态
- document.querySelectorAll('.nav-link').forEach(link => {
- link.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');
+ // 显示选中的内容区域
+ const targetSection = document.getElementById(sectionName + '-section');
+ if (targetSection) {
+ targetSection.classList.add('active');
+ console.log('页面已激活:', sectionName + '-section'); // 调试信息
+ } else {
+ console.error('找不到页面元素:', sectionName + '-section'); // 调试信息
}
- });
- // 根据不同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();
+ // 设置对应菜单项为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');
}
- }, 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';
+ // 根据不同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');
+ document.getElementById('sidebar').classList.toggle('show');
}
+// ==================== 仪表盘管理 ====================
+
// 加载仪表盘数据
async function loadDashboard() {
- try {
- toggleLoading(true);
+ 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: {
+ // 获取账号列表
+ 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++;
+ }
+ }
});
- 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.totalKeywords = totalKeywords;
- 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++;
- }
+ // 更新仪表盘显示
+ updateDashboardStats(accountsWithKeywords.length, totalKeywords, enabledAccounts);
+ updateDashboardAccountsList(accountsWithKeywords);
}
- });
-
- dashboardData.totalKeywords = totalKeywords;
-
- // 更新仪表盘显示
- updateDashboardStats(accountsWithKeywords.length, totalKeywords, enabledAccounts);
- updateDashboardAccountsList(accountsWithKeywords);
+ } catch (error) {
+ console.error('加载仪表盘数据失败:', error);
+ showToast('加载仪表盘数据失败', 'danger');
+ } finally {
+ toggleLoading(false);
}
- } 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;
+ 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 = '';
+ 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 = '未配置';
+ if (accounts.length === 0) {
+ tbody.innerHTML = `
+
+
+
+ 暂无账号数据
+ |
+
+ `;
+ return;
}
- const row = document.createElement('tr');
- row.className = isEnabled ? '' : 'table-secondary';
- row.innerHTML = `
-
- ${account.id}
- ${!isEnabled ? '' : ''}
- |
-
- ${keywordCount} 个关键词
- |
- ${status} |
-
- ${new Date().toLocaleString()}
- |
- `;
- tbody.appendChild(row);
- });
+ 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();
+ 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;
+ // 检查缓存
+ 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;
}
- } catch (error) {
- console.error(`获取账号 ${accountId} 关键词失败:`, error);
- return 0;
- }
}
// 清除关键词缓存
function clearKeywordCache() {
- accountKeywordCache = {};
- cacheTimestamp = 0;
+ accountKeywordCache = {};
+ cacheTimestamp = 0;
}
+// ==================== 账号列表管理 ====================
+
// 刷新账号列表(用于自动回复页面)
async function refreshAccountList() {
- try {
- toggleLoading(true);
+ 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: {
+ // 获取账号列表
+ 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;
});
- if (keywordsResponse.ok) {
- const keywordsData = await keywordsResponse.json();
- return {
- ...account,
- keywords: keywordsData,
- keywordCount: keywordsData.length
- };
- } else {
- return {
- ...account,
- keywordCount: 0
- };
+ // 渲染启用的账号
+ 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);
+ });
}
- } 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} 个关键词)`;
+ console.log('账号列表刷新完成,关键词统计:', accountsWithKeywords.map(a => ({ id: a.id, keywords: a.keywordCount })));
} else {
- icon = '🟡';
- status = ` (${account.keywordCount} 个关键词)`;
+ showToast('获取账号列表失败', 'danger');
}
-
- 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);
}
- } catch (error) {
- console.error('刷新账号列表失败:', error);
- showToast('刷新账号列表失败', 'danger');
- } finally {
- toggleLoading(false);
- }
}
// 加载账号关键词
@@ -875,163 +883,167 @@ async function deleteKeyword(cookieId, index) {
}
}
+// ==================== 通用工具函数 ====================
+
// 显示/隐藏加载动画
function toggleLoading(show) {
- document.getElementById('loading').classList.toggle('d-none', !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');
+ 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 = `
-
- `;
+ toast.innerHTML = `
+
+ `;
- toastContainer.appendChild(toast);
- const bsToast = new bootstrap.Toast(toast, { delay: 3000 });
- bsToast.show();
+ toastContainer.appendChild(toast);
+ const bsToast = new bootstrap.Toast(toast, { delay: 3000 });
+ bsToast.show();
- // 自动移除
- toast.addEventListener('hidden.bs.toast', () => {
- toast.remove();
- });
+ // 自动移除
+ toast.addEventListener('hidden.bs.toast', () => {
+ toast.remove();
+ });
}
// 错误处理
async function handleApiError(err) {
- console.error(err);
- showToast(err.message || '操作失败', 'danger');
- toggleLoading(false);
+ 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;
- }
+ toggleLoading(true);
+ try {
+ // 添加认证头
+ if (authToken) {
+ opts.headers = opts.headers || {};
+ opts.headers['Authorization'] = `Bearer ${authToken}`;
}
- } catch {
- errorMessage = `HTTP ${res.status} ${res.statusText}`;
- }
- throw new Error(errorMessage);
+
+ 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;
}
- const data = await res.json();
- toggleLoading(false);
- return data;
- } catch (err) {
- handleApiError(err);
- throw err;
- }
}
+// ==================== Cookie管理 ====================
+
// 加载Cookie列表
async function loadCookies() {
- try {
- toggleLoading(true);
- const tbody = document.querySelector('#cookieTable tbody');
- tbody.innerHTML = '';
+ try {
+ toggleLoading(true);
+ const tbody = document.querySelector('#cookieTable tbody');
+ tbody.innerHTML = '';
- const cookieDetails = await fetchJSON(apiBase + '/cookies/details');
+ 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' }
- };
+ 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 => {
// 使用数据库中的实际状态,默认为启用
@@ -1181,72 +1193,77 @@ async function delCookie(id) {
}
}
-// 内联编辑Cookie
-function editCookieInline(id, currentValue) {
- const row = event.target.closest('tr');
- const cookieValueCell = row.querySelector('.cookie-value');
- const originalContent = cookieValueCell.innerHTML;
+/**
+ * 内联编辑Cookie
+ * @param {string} id - 账号ID
+ * @param {string} currentValue - 当前Cookie值
+ * @param {Event} event - 点击事件对象
+ */
+function editCookieInline(id, currentValue, event) {
+ const row = event.target.closest('tr');
+ const cookieValueCell = row.querySelector('.cookie-value');
+ const originalContent = cookieValueCell.innerHTML;
- // 存储原始数据到全局变量,避免HTML注入问题
- window.editingCookieData = {
- id: id,
- originalContent: originalContent,
- originalValue: currentValue || ''
- };
+ // 存储原始数据到全局变量,避免HTML注入问题
+ window.editingCookieData = {
+ id: id,
+ originalContent: originalContent,
+ originalValue: currentValue || ''
+ };
- // 创建编辑界面容器
- const editContainer = document.createElement('div');
- editContainer.className = 'd-flex gap-2';
+ // 创建编辑界面容器
+ 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 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 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);
+ // 创建取消按钮
+ 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);
+ // 组装编辑界面
+ editContainer.appendChild(input);
+ editContainer.appendChild(saveBtn);
+ editContainer.appendChild(cancelBtn);
- // 替换原内容
- cookieValueCell.innerHTML = '';
- cookieValueCell.appendChild(editContainer);
+ // 替换原内容
+ cookieValueCell.innerHTML = '';
+ cookieValueCell.appendChild(editContainer);
- // 聚焦输入框
- input.focus();
- input.select();
+ // 聚焦输入框
+ 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);
- }
- });
+ // 添加键盘事件监听
+ 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);
+ // 禁用该行的其他按钮
+ const actionButtons = row.querySelectorAll('.btn-group button');
+ actionButtons.forEach(btn => btn.disabled = true);
}
// 保存内联编辑的Cookie
@@ -1562,110 +1579,113 @@ async function checkAuth() {
}
}
+// ==================== 应用初始化 ====================
+
// 初始化事件监听
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();
+ // 首先检查认证状态
+ const isAuthenticated = await checkAuth();
+ if (!isAuthenticated) return;
- if (!id || !value) 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();
- try {
- await fetchJSON(apiBase + '/cookies', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ id, value })
- });
+ if (!id || !value) return;
- document.getElementById('cookieId').value = '';
- document.getElementById('cookieValue').value = '';
- showToast(`账号 "${id}" 添加成功`);
- loadCookies();
- } catch (err) {
- // 错误已在fetchJSON中处理
- }
- });
+ try {
+ await fetchJSON(apiBase + '/cookies', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ id, value })
+ });
- // 增强的键盘快捷键和用户体验
- document.getElementById('newKeyword')?.addEventListener('keypress', function(e) {
- if (e.key === 'Enter') {
- e.preventDefault();
- document.getElementById('newReply').focus();
- }
- });
+ document.getElementById('cookieId').value = '';
+ document.getElementById('cookieValue').value = '';
+ showToast(`账号 "${id}" 添加成功`);
+ loadCookies();
+ } catch (err) {
+ // 错误已在fetchJSON中处理
+ }
+ });
- document.getElementById('newReply')?.addEventListener('keypress', function(e) {
- if (e.key === 'Enter') {
- e.preventDefault();
- addKeyword();
- }
- });
+ // 增强的键盘快捷键和用户体验
+ document.getElementById('newKeyword')?.addEventListener('keypress', function (e) {
+ if (e.key === 'Enter') {
+ e.preventDefault();
+ document.getElementById('newReply').focus();
+ }
+ });
- // ESC键取消编辑
- document.addEventListener('keydown', function(e) {
- if (e.key === 'Escape' && typeof window.editingIndex !== 'undefined') {
- e.preventDefault();
- cancelEdit();
- }
- });
+ document.getElementById('newReply')?.addEventListener('keypress', function (e) {
+ if (e.key === 'Enter') {
+ e.preventDefault();
+ addKeyword();
+ }
+ });
- // 输入框实时验证和提示
- document.getElementById('newKeyword')?.addEventListener('input', function(e) {
- const value = e.target.value.trim();
- const addBtn = document.querySelector('.add-btn');
- const replyInput = document.getElementById('newReply');
+ // ESC键取消编辑
+ document.addEventListener('keydown', function (e) {
+ if (e.key === 'Escape' && typeof window.editingIndex !== 'undefined') {
+ e.preventDefault();
+ cancelEdit();
+ }
+ });
- 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('newKeyword')?.addEventListener('input', function (e) {
+ const value = e.target.value.trim();
+ const addBtn = document.querySelector('.add-btn');
+ const replyInput = document.getElementById('newReply');
- 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 (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)';
+ }
+ });
- 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)';
- }
- });
+ document.getElementById('newReply')?.addEventListener('input', function (e) {
+ const value = e.target.value.trim();
+ const addBtn = document.querySelector('.add-btn');
+ const keywordInput = document.getElementById('newKeyword');
- // 初始加载仪表盘
- loadDashboard();
+ 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)';
+ }
+ });
- // 点击侧边栏外部关闭移动端菜单
- document.addEventListener('click', function(e) {
- const sidebar = document.getElementById('sidebar');
- const toggle = document.querySelector('.mobile-toggle');
+ // 初始加载仪表盘
+ loadDashboard();
- if (window.innerWidth <= 768 &&
- !sidebar.contains(e.target) &&
- !toggle.contains(e.target) &&
- sidebar.classList.contains('show')) {
- sidebar.classList.remove('show');
- }
- });
+ // 点击侧边栏外部关闭移动端菜单
+ 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');
+ }
+ });
});
// ==================== 默认回复管理功能 ====================
@@ -1857,9 +1877,13 @@ async function saveDefaultReply() {
}
}
-// 测试默认回复(占位函数)
+/**
+ * 测试默认回复(占位函数)
+ * @param {string} accountId - 账号ID
+ */
function testDefaultReply(accountId) {
- showToast('测试功能开发中...', 'info');
+ console.log('测试默认回复功能,账号ID:', accountId);
+ showToast('测试功能开发中...', 'info');
}
// ==================== AI回复配置相关函数 ====================