diff --git a/db_manager.py b/db_manager.py index d3e04b9..7bed531 100644 --- a/db_manager.py +++ b/db_manager.py @@ -1183,14 +1183,18 @@ class DBManager: "INSERT INTO keywords (cookie_id, keyword, reply, item_id, type) VALUES (?, ?, ?, ?, 'text')", (cookie_id, keyword, reply, normalized_item_id)) except sqlite3.IntegrityError as ie: - # 如果遇到唯一约束冲突,记录详细错误信息 + # 如果遇到唯一约束冲突,记录详细错误信息并回滚 item_desc = f"商品ID: {normalized_item_id}" if normalized_item_id else "通用关键词" logger.error(f"关键词唯一约束冲突: Cookie={cookie_id}, 关键词='{keyword}', {item_desc}") + self.conn.rollback() raise ie self.conn.commit() logger.info(f"文本关键字保存成功: {cookie_id}, {len(keywords)}条,图片关键词已保留") return True + except sqlite3.IntegrityError: + # 唯一约束冲突,重新抛出异常让上层处理 + raise except Exception as e: logger.error(f"文本关键字保存失败: {e}") self.conn.rollback() diff --git a/reply_server.py b/reply_server.py index bf7a406..8dae2f5 100644 --- a/reply_server.py +++ b/reply_server.py @@ -1631,10 +1631,22 @@ def update_keywords_with_item_id(cid: str, body: KeywordWithItemIdIn, current_us keywords_to_save.append((keyword, reply, item_id)) - # 保存关键词 - success = db_manager.save_keywords_with_item_id(cid, keywords_to_save) - if not success: - raise HTTPException(status_code=500, detail="保存关键词失败") + # 保存关键词(只保存文本关键词,保留图片关键词) + try: + success = db_manager.save_text_keywords_only(cid, keywords_to_save) + if not success: + raise HTTPException(status_code=500, detail="保存关键词失败") + except Exception as e: + error_msg = str(e) + if "UNIQUE constraint failed" in error_msg: + # 解析具体的冲突信息 + if "keywords.cookie_id, keywords.keyword" in error_msg: + raise HTTPException(status_code=400, detail="关键词重复!该关键词已存在(可能是图片关键词或文本关键词),请使用其他关键词") + else: + raise HTTPException(status_code=400, detail="关键词重复!请使用不同的关键词或商品ID组合") + else: + log_with_user('error', f"保存关键词时发生未知错误: {error_msg}", current_user) + raise HTTPException(status_code=500, detail="保存关键词失败") log_with_user('info', f"更新Cookie关键字(含商品ID): {cid}, 数量: {len(keywords_to_save)}", current_user) return {"msg": "updated", "count": len(keywords_to_save)} diff --git a/static/js/app.js b/static/js/app.js index 6c2828a..9c2895d 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -424,6 +424,42 @@ async function refreshAccountList() { } } +// 只刷新关键词列表(不重新加载商品列表等其他数据) +async function refreshKeywordsList() { + if (!currentCookieId) { + console.warn('没有选中的账号,无法刷新关键词列表'); + return; + } + + try { + const response = await fetch(`${apiBase}/keywords-with-item-id/${currentCookieId}`, { + headers: { + 'Authorization': `Bearer ${authToken}` + } + }); + + if (response.ok) { + const data = await response.json(); + console.log('刷新关键词列表,从服务器获取的数据:', data); + + // 更新缓存数据 + keywordsData[currentCookieId] = data; + + // 只重新渲染关键词列表 + renderKeywordsList(data); + + // 清除关键词缓存 + clearKeywordCache(); + } else { + console.error('刷新关键词列表失败:', response.status); + showToast('刷新关键词列表失败', 'danger'); + } + } catch (error) { + console.error('刷新关键词列表失败:', error); + showToast('刷新关键词列表失败', 'danger'); + } +} + // 加载账号关键词 async function loadAccountKeywords() { const accountId = document.getElementById('accountSelect').value; @@ -588,22 +624,32 @@ async function addKeyword() { currentKeywords.splice(window.editingIndex, 1); } - // 准备要保存的关键词列表 - let keywordsToSave = [...currentKeywords]; + // 准备要保存的关键词列表(只包含文本类型的关键字) + let textKeywords = currentKeywords.filter(item => (item.type || 'text') === 'text'); // 如果是编辑模式,先移除原关键词 if (isEditMode && typeof window.editingIndex !== 'undefined') { - keywordsToSave.splice(window.editingIndex, 1); + // 需要重新计算在文本关键字中的索引 + const originalKeyword = keywordsData[currentCookieId][window.editingIndex]; + const textIndex = textKeywords.findIndex(item => + item.keyword === originalKeyword.keyword && + (item.item_id || '') === (originalKeyword.item_id || '') + ); + if (textIndex !== -1) { + textKeywords.splice(textIndex, 1); + } } - // 检查关键词是否已存在(考虑商品ID) - const existingKeyword = keywordsToSave.find(item => + // 检查关键词是否已存在(考虑商品ID,检查所有类型的关键词) + const allKeywords = keywordsData[currentCookieId] || []; + const existingKeyword = allKeywords.find(item => item.keyword === keyword && (item.item_id || '') === (itemId || '') ); if (existingKeyword) { const itemIdText = itemId ? `(商品ID: ${itemId})` : '(通用关键词)'; - showToast(`关键词 "${keyword}" ${itemIdText} 已存在,请使用其他关键词或商品ID`, 'warning'); + const typeText = existingKeyword.type === 'image' ? '图片' : '文本'; + showToast(`关键词 "${keyword}" ${itemIdText} 已存在(${typeText}关键词),请使用其他关键词或商品ID`, 'warning'); toggleLoading(false); return; } @@ -614,7 +660,7 @@ async function addKeyword() { reply: reply, item_id: itemId || '' }; - keywordsToSave.push(newKeyword); + textKeywords.push(newKeyword); const response = await fetch(`${apiBase}/keywords-with-item-id/${currentCookieId}`, { method: 'POST', @@ -623,7 +669,7 @@ async function addKeyword() { 'Authorization': `Bearer ${authToken}` }, body: JSON.stringify({ - keywords: keywordsToSave + keywords: textKeywords }) }); @@ -667,12 +713,26 @@ async function addKeyword() { keywordInput.focus(); }, 100); - loadAccountKeywords(); // 重新加载关键词列表 - clearKeywordCache(); // 清除缓存 + // 只刷新关键词列表,不重新加载整个界面 + await refreshKeywordsList(); } else { - const errorText = await response.text(); - console.error('关键词添加失败:', errorText); - showToast('关键词添加失败', 'danger'); + try { + const errorData = await response.json(); + const errorMessage = errorData.detail || '关键词添加失败'; + console.error('关键词添加失败:', errorMessage); + + // 检查是否是重复关键词的错误 + if (errorMessage.includes('关键词已存在') || errorMessage.includes('关键词重复') || errorMessage.includes('UNIQUE constraint')) { + showToast(`❌ 关键词重复:${errorMessage}`, 'warning'); + } else { + showToast(`❌ ${errorMessage}`, 'danger'); + } + } catch (parseError) { + // 如果无法解析JSON,使用原始文本 + const errorText = await response.text(); + console.error('关键词添加失败:', errorText); + showToast('❌ 关键词添加失败', 'danger'); + } } } catch (error) { console.error('添加关键词失败:', error); @@ -893,9 +953,8 @@ async function deleteKeyword(cookieId, index) { if (response.ok) { showToast('关键词删除成功', 'success'); - // 重新加载关键词列表 - loadAccountKeywords(); - clearKeywordCache(); // 清除缓存 + // 只刷新关键词列表,不重新加载整个界面 + await refreshKeywordsList(); } else { const errorText = await response.text(); console.error('关键词删除失败:', errorText); @@ -6134,16 +6193,17 @@ async function addImageKeyword() { const modal = bootstrap.Modal.getInstance(document.getElementById('addImageKeywordModal')); modal.hide(); - // 重新加载关键词列表 - loadAccountKeywords(); - clearKeywordCache(); + // 只刷新关键词列表,不重新加载整个界面 + await refreshKeywordsList(); } else { try { const errorData = await response.json(); let errorMessage = errorData.detail || '图片关键词添加失败'; // 根据不同的错误类型提供更友好的提示 - if (errorMessage.includes('图片尺寸过大')) { + if (errorMessage.includes('关键词') && (errorMessage.includes('已存在') || errorMessage.includes('重复'))) { + errorMessage = `❌ 关键词重复:${errorMessage}`; + } else if (errorMessage.includes('图片尺寸过大')) { errorMessage = '❌ 图片尺寸过大,请选择尺寸较小的图片(建议不超过4096x4096像素)'; } else if (errorMessage.includes('图片像素总数过大')) { errorMessage = '❌ 图片像素总数过大,请选择分辨率较低的图片';