mirror of
https://github.com/zhinianboke/xianyu-auto-reply.git
synced 2025-08-30 09:37:35 +08:00
支持指定商品默认回复
This commit is contained in:
parent
46f7066519
commit
5b2a991f41
@ -1010,12 +1010,36 @@ class XianyuLive:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"调试消息结构时发生错误: {self._safe_str(e)}")
|
logger.error(f"调试消息结构时发生错误: {self._safe_str(e)}")
|
||||||
|
|
||||||
async def get_default_reply(self, send_user_name: str, send_user_id: str, send_message: str, chat_id: str = None) -> str:
|
async def get_default_reply(self, send_user_name: str, send_user_id: str, send_message: str, chat_id: str, item_id: str = None) -> str:
|
||||||
"""获取默认回复内容,支持变量替换和只回复一次功能"""
|
"""获取默认回复内容,支持指定商品回复、变量替换和只回复一次功能"""
|
||||||
try:
|
try:
|
||||||
from db_manager import db_manager
|
from db_manager import db_manager
|
||||||
|
|
||||||
# 获取当前账号的默认回复设置
|
# 1. 优先检查指定商品回复
|
||||||
|
if item_id:
|
||||||
|
item_reply = db_manager.get_item_reply(self.cookie_id, item_id)
|
||||||
|
if item_reply and item_reply.get('reply_content'):
|
||||||
|
reply_content = item_reply['reply_content']
|
||||||
|
logger.info(f"【{self.cookie_id}】使用指定商品回复: 商品ID={item_id}")
|
||||||
|
|
||||||
|
# 进行变量替换
|
||||||
|
try:
|
||||||
|
formatted_reply = reply_content.format(
|
||||||
|
send_user_name=send_user_name,
|
||||||
|
send_user_id=send_user_id,
|
||||||
|
send_message=send_message,
|
||||||
|
item_id=item_id
|
||||||
|
)
|
||||||
|
logger.info(f"【{self.cookie_id}】指定商品回复内容: {formatted_reply}")
|
||||||
|
return formatted_reply
|
||||||
|
except Exception as format_error:
|
||||||
|
logger.error(f"指定商品回复变量替换失败: {self._safe_str(format_error)}")
|
||||||
|
# 如果变量替换失败,返回原始内容
|
||||||
|
return reply_content
|
||||||
|
else:
|
||||||
|
logger.debug(f"【{self.cookie_id}】商品ID {item_id} 没有配置指定回复,使用默认回复")
|
||||||
|
|
||||||
|
# 2. 获取当前账号的默认回复设置
|
||||||
default_reply_settings = db_manager.get_default_reply(self.cookie_id)
|
default_reply_settings = db_manager.get_default_reply(self.cookie_id)
|
||||||
|
|
||||||
if not default_reply_settings or not default_reply_settings.get('enabled', False):
|
if not default_reply_settings or not default_reply_settings.get('enabled', False):
|
||||||
@ -3205,7 +3229,7 @@ class XianyuLive:
|
|||||||
reply_source = 'AI' # 标记为AI回复
|
reply_source = 'AI' # 标记为AI回复
|
||||||
else:
|
else:
|
||||||
# 3. 最后使用默认回复
|
# 3. 最后使用默认回复
|
||||||
reply = await self.get_default_reply(send_user_name, send_user_id, send_message, chat_id)
|
reply = await self.get_default_reply(send_user_name, send_user_id, send_message, chat_id, item_id)
|
||||||
if reply == "EMPTY_REPLY":
|
if reply == "EMPTY_REPLY":
|
||||||
# 默认回复内容为空,不进行任何回复
|
# 默认回复内容为空,不进行任何回复
|
||||||
logger.info(f"[{msg_time}] 【{self.cookie_id}】默认回复内容为空,跳过自动回复")
|
logger.info(f"[{msg_time}] 【{self.cookie_id}】默认回复内容为空,跳过自动回复")
|
||||||
|
171
check_user_credentials.py
Normal file
171
check_user_credentials.py
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
检查用户凭据
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import hashlib
|
||||||
|
|
||||||
|
# 添加项目根目录到路径
|
||||||
|
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
|
from db_manager import db_manager
|
||||||
|
|
||||||
|
|
||||||
|
def check_users():
|
||||||
|
"""检查用户表中的用户信息"""
|
||||||
|
print("🔍 检查用户表中的用户信息")
|
||||||
|
print("=" * 50)
|
||||||
|
|
||||||
|
try:
|
||||||
|
cursor = db_manager.conn.cursor()
|
||||||
|
cursor.execute("SELECT id, username, password_hash, is_admin FROM users")
|
||||||
|
users = cursor.fetchall()
|
||||||
|
|
||||||
|
print(f"用户表中共有 {len(users)} 个用户:")
|
||||||
|
for user in users:
|
||||||
|
print(f" - 用户ID: {user[0]}")
|
||||||
|
print(f" 用户名: {user[1]}")
|
||||||
|
print(f" 密码哈希: {user[2][:20]}..." if user[2] else " 密码哈希: None")
|
||||||
|
print(f" 是否管理员: {user[3]}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
# 测试密码验证
|
||||||
|
if users:
|
||||||
|
test_user = users[0]
|
||||||
|
username = test_user[1]
|
||||||
|
stored_hash = test_user[2]
|
||||||
|
|
||||||
|
print(f"🔐 测试用户 '{username}' 的密码验证:")
|
||||||
|
|
||||||
|
# 测试常见密码
|
||||||
|
test_passwords = ["admin123", "admin", "123456", "password"]
|
||||||
|
|
||||||
|
for password in test_passwords:
|
||||||
|
# 计算密码哈希
|
||||||
|
password_hash = hashlib.sha256(password.encode()).hexdigest()
|
||||||
|
|
||||||
|
if password_hash == stored_hash:
|
||||||
|
print(f"✅ 密码 '{password}' 匹配!")
|
||||||
|
return username, password
|
||||||
|
else:
|
||||||
|
print(f"❌ 密码 '{password}' 不匹配")
|
||||||
|
print(f" 计算哈希: {password_hash[:20]}...")
|
||||||
|
print(f" 存储哈希: {stored_hash[:20]}...")
|
||||||
|
|
||||||
|
print("⚠️ 没有找到匹配的密码")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ 检查用户失败: {e}")
|
||||||
|
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
|
||||||
|
def test_login_with_correct_credentials():
|
||||||
|
"""使用正确的凭据测试登录"""
|
||||||
|
username, password = check_users()
|
||||||
|
|
||||||
|
if username and password:
|
||||||
|
print(f"\n🌐 使用正确凭据测试登录")
|
||||||
|
print("=" * 50)
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
try:
|
||||||
|
login_data = {
|
||||||
|
"username": username,
|
||||||
|
"password": password
|
||||||
|
}
|
||||||
|
response = requests.post("http://localhost:8080/login", json=login_data, timeout=10)
|
||||||
|
print(f"登录状态码: {response.status_code}")
|
||||||
|
print(f"登录响应: {response.text}")
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
data = response.json()
|
||||||
|
if data.get('success'):
|
||||||
|
token = data.get('token') or data.get('access_token')
|
||||||
|
print(f"✅ 登录成功!Token: {token[:20]}..." if token else "✅ 登录成功但没有token")
|
||||||
|
|
||||||
|
if token:
|
||||||
|
# 测试cookies/details接口
|
||||||
|
headers = {"Authorization": f"Bearer {token}"}
|
||||||
|
response = requests.get("http://localhost:8080/cookies/details", headers=headers, timeout=10)
|
||||||
|
print(f"cookies/details状态码: {response.status_code}")
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
details = response.json()
|
||||||
|
print(f"✅ 获取到 {len(details)} 个账号详情")
|
||||||
|
for detail in details:
|
||||||
|
print(f" - {detail['id']}: {'启用' if detail['enabled'] else '禁用'}")
|
||||||
|
else:
|
||||||
|
print(f"❌ 获取账号详情失败: {response.text}")
|
||||||
|
else:
|
||||||
|
print(f"❌ 登录失败: {data.get('message', '未知错误')}")
|
||||||
|
else:
|
||||||
|
print(f"❌ 登录请求失败: {response.status_code}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ 登录测试失败: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
def create_test_user():
|
||||||
|
"""创建测试用户"""
|
||||||
|
print(f"\n🔧 创建测试用户")
|
||||||
|
print("=" * 50)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 创建一个新的测试用户
|
||||||
|
test_username = "testuser"
|
||||||
|
test_password = "test123"
|
||||||
|
password_hash = hashlib.sha256(test_password.encode()).hexdigest()
|
||||||
|
|
||||||
|
cursor = db_manager.conn.cursor()
|
||||||
|
cursor.execute("""
|
||||||
|
INSERT OR REPLACE INTO users (username, password_hash, is_admin)
|
||||||
|
VALUES (?, ?, ?)
|
||||||
|
""", (test_username, password_hash, 1))
|
||||||
|
db_manager.conn.commit()
|
||||||
|
|
||||||
|
print(f"✅ 创建测试用户成功:")
|
||||||
|
print(f" 用户名: {test_username}")
|
||||||
|
print(f" 密码: {test_password}")
|
||||||
|
print(f" 密码哈希: {password_hash[:20]}...")
|
||||||
|
|
||||||
|
return test_username, test_password
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ 创建测试用户失败: {e}")
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print("🚀 检查用户凭据和登录问题")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
# 检查现有用户
|
||||||
|
username, password = check_users()
|
||||||
|
|
||||||
|
if not username:
|
||||||
|
# 如果没有找到有效用户,创建一个测试用户
|
||||||
|
print("\n没有找到有效的用户凭据,创建测试用户...")
|
||||||
|
username, password = create_test_user()
|
||||||
|
|
||||||
|
if username and password:
|
||||||
|
# 使用正确凭据测试登录
|
||||||
|
test_login_with_correct_credentials()
|
||||||
|
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
print("🎯 检查完成!")
|
||||||
|
print("\n📋 总结:")
|
||||||
|
print("1. 检查了用户表中的用户信息")
|
||||||
|
print("2. 验证了密码哈希")
|
||||||
|
print("3. 测试了登录功能")
|
||||||
|
print("4. 测试了cookies/details接口")
|
||||||
|
|
||||||
|
if username and password:
|
||||||
|
print(f"\n✅ 可用的登录凭据:")
|
||||||
|
print(f" 用户名: {username}")
|
||||||
|
print(f" 密码: {password}")
|
||||||
|
print(f"\n请使用这些凭据登录管理后台测试账号下拉框功能")
|
192
check_user_table.py
Normal file
192
check_user_table.py
Normal file
@ -0,0 +1,192 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
检查用户表结构
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import hashlib
|
||||||
|
|
||||||
|
# 添加项目根目录到路径
|
||||||
|
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
|
from db_manager import db_manager
|
||||||
|
|
||||||
|
|
||||||
|
def check_user_table_structure():
|
||||||
|
"""检查用户表结构"""
|
||||||
|
print("🔍 检查用户表结构")
|
||||||
|
print("=" * 50)
|
||||||
|
|
||||||
|
try:
|
||||||
|
cursor = db_manager.conn.cursor()
|
||||||
|
|
||||||
|
# 检查用户表是否存在
|
||||||
|
cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='users'")
|
||||||
|
table_exists = cursor.fetchone()
|
||||||
|
|
||||||
|
if table_exists:
|
||||||
|
print("✅ users表存在")
|
||||||
|
|
||||||
|
# 检查表结构
|
||||||
|
cursor.execute("PRAGMA table_info(users)")
|
||||||
|
columns = cursor.fetchall()
|
||||||
|
print("users表结构:")
|
||||||
|
for col in columns:
|
||||||
|
print(f" - {col[1]} ({col[2]}) - {'NOT NULL' if col[3] else 'NULL'} - {'PRIMARY KEY' if col[5] else ''}")
|
||||||
|
|
||||||
|
# 查看表中的数据
|
||||||
|
cursor.execute("SELECT * FROM users")
|
||||||
|
users = cursor.fetchall()
|
||||||
|
print(f"\nusers表中共有 {len(users)} 条记录:")
|
||||||
|
for i, user in enumerate(users):
|
||||||
|
print(f" 记录{i+1}: {user}")
|
||||||
|
|
||||||
|
else:
|
||||||
|
print("❌ users表不存在")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ 检查用户表失败: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
def fix_user_table():
|
||||||
|
"""修复用户表"""
|
||||||
|
print("\n🔧 修复用户表")
|
||||||
|
print("=" * 50)
|
||||||
|
|
||||||
|
try:
|
||||||
|
cursor = db_manager.conn.cursor()
|
||||||
|
|
||||||
|
# 检查是否有is_admin列
|
||||||
|
cursor.execute("PRAGMA table_info(users)")
|
||||||
|
columns = cursor.fetchall()
|
||||||
|
column_names = [col[1] for col in columns]
|
||||||
|
|
||||||
|
if 'is_admin' not in column_names:
|
||||||
|
print("添加is_admin列...")
|
||||||
|
cursor.execute("ALTER TABLE users ADD COLUMN is_admin INTEGER DEFAULT 1")
|
||||||
|
db_manager.conn.commit()
|
||||||
|
print("✅ 添加is_admin列成功")
|
||||||
|
|
||||||
|
# 检查是否有用户数据
|
||||||
|
cursor.execute("SELECT COUNT(*) FROM users")
|
||||||
|
user_count = cursor.fetchone()[0]
|
||||||
|
|
||||||
|
if user_count == 0:
|
||||||
|
print("创建默认管理员用户...")
|
||||||
|
username = "admin"
|
||||||
|
password = "admin123"
|
||||||
|
password_hash = hashlib.sha256(password.encode()).hexdigest()
|
||||||
|
|
||||||
|
cursor.execute("""
|
||||||
|
INSERT INTO users (username, password_hash, is_admin)
|
||||||
|
VALUES (?, ?, ?)
|
||||||
|
""", (username, password_hash, 1))
|
||||||
|
db_manager.conn.commit()
|
||||||
|
|
||||||
|
print(f"✅ 创建默认用户成功:")
|
||||||
|
print(f" 用户名: {username}")
|
||||||
|
print(f" 密码: {password}")
|
||||||
|
|
||||||
|
return username, password
|
||||||
|
else:
|
||||||
|
# 获取第一个用户并重置密码
|
||||||
|
cursor.execute("SELECT username FROM users LIMIT 1")
|
||||||
|
username = cursor.fetchone()[0]
|
||||||
|
password = "admin123"
|
||||||
|
password_hash = hashlib.sha256(password.encode()).hexdigest()
|
||||||
|
|
||||||
|
cursor.execute("""
|
||||||
|
UPDATE users SET password_hash = ?, is_admin = 1
|
||||||
|
WHERE username = ?
|
||||||
|
""", (password_hash, username))
|
||||||
|
db_manager.conn.commit()
|
||||||
|
|
||||||
|
print(f"✅ 重置用户密码成功:")
|
||||||
|
print(f" 用户名: {username}")
|
||||||
|
print(f" 密码: {password}")
|
||||||
|
|
||||||
|
return username, password
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ 修复用户表失败: {e}")
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
|
||||||
|
def test_login_after_fix():
|
||||||
|
"""修复后测试登录"""
|
||||||
|
username, password = fix_user_table()
|
||||||
|
|
||||||
|
if username and password:
|
||||||
|
print(f"\n🌐 测试修复后的登录")
|
||||||
|
print("=" * 50)
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
try:
|
||||||
|
login_data = {
|
||||||
|
"username": username,
|
||||||
|
"password": password
|
||||||
|
}
|
||||||
|
response = requests.post("http://localhost:8080/login", json=login_data, timeout=10)
|
||||||
|
print(f"登录状态码: {response.status_code}")
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
data = response.json()
|
||||||
|
print(f"登录响应: {data}")
|
||||||
|
|
||||||
|
if data.get('success'):
|
||||||
|
token = data.get('token') or data.get('access_token')
|
||||||
|
print(f"✅ 登录成功!")
|
||||||
|
|
||||||
|
if token:
|
||||||
|
print(f"Token: {token[:20]}...")
|
||||||
|
|
||||||
|
# 测试cookies/details接口
|
||||||
|
headers = {"Authorization": f"Bearer {token}"}
|
||||||
|
response = requests.get("http://localhost:8080/cookies/details", headers=headers, timeout=10)
|
||||||
|
print(f"cookies/details状态码: {response.status_code}")
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
details = response.json()
|
||||||
|
print(f"✅ 获取到 {len(details)} 个账号详情")
|
||||||
|
|
||||||
|
if len(details) > 0:
|
||||||
|
print("🎉 账号下拉框应该有数据了!")
|
||||||
|
print("账号列表:")
|
||||||
|
for detail in details:
|
||||||
|
status = "🟢" if detail['enabled'] else "🔴"
|
||||||
|
print(f" {status} {detail['id']}")
|
||||||
|
else:
|
||||||
|
print("⚠️ 账号列表为空,但API接口正常")
|
||||||
|
else:
|
||||||
|
print(f"❌ 获取账号详情失败: {response.text}")
|
||||||
|
else:
|
||||||
|
print("⚠️ 登录成功但没有获取到token")
|
||||||
|
else:
|
||||||
|
print(f"❌ 登录失败: {data.get('message', '未知错误')}")
|
||||||
|
else:
|
||||||
|
print(f"❌ 登录请求失败: {response.status_code} - {response.text}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ 登录测试失败: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print("🚀 检查和修复用户表")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
# 检查用户表结构
|
||||||
|
check_user_table_structure()
|
||||||
|
|
||||||
|
# 修复用户表并测试登录
|
||||||
|
test_login_after_fix()
|
||||||
|
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
print("🎯 修复完成!")
|
||||||
|
print("\n📋 现在可以:")
|
||||||
|
print("1. 使用 admin/admin123 登录管理后台")
|
||||||
|
print("2. 进入指定商品回复界面")
|
||||||
|
print("3. 检查账号下拉框是否有数据")
|
||||||
|
print("4. 如果仍然没有数据,请检查浏览器开发者工具")
|
204
db_manager.py
204
db_manager.py
@ -315,6 +315,18 @@ class DBManager:
|
|||||||
if "duplicate column name" not in str(e).lower():
|
if "duplicate column name" not in str(e).lower():
|
||||||
logger.warning(f"添加 reply_once 字段失败: {e}")
|
logger.warning(f"添加 reply_once 字段失败: {e}")
|
||||||
|
|
||||||
|
# 创建指定商品回复表
|
||||||
|
cursor.execute('''
|
||||||
|
CREATE TABLE IF NOT EXISTS item_replay (
|
||||||
|
item_id TEXT NOT NULL PRIMARY KEY,
|
||||||
|
cookie_id TEXT NOT NULL,
|
||||||
|
reply_content TEXT NOT NULL ,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (item_id) REFERENCES cookies(id) ON DELETE CASCADE
|
||||||
|
)
|
||||||
|
''')
|
||||||
|
|
||||||
# 创建默认回复记录表(记录已回复的chat_id)
|
# 创建默认回复记录表(记录已回复的chat_id)
|
||||||
cursor.execute('''
|
cursor.execute('''
|
||||||
CREATE TABLE IF NOT EXISTS default_reply_records (
|
CREATE TABLE IF NOT EXISTS default_reply_records (
|
||||||
@ -4255,6 +4267,198 @@ class DBManager:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"升级keywords表失败: {e}")
|
logger.error(f"升级keywords表失败: {e}")
|
||||||
raise
|
raise
|
||||||
|
def get_item_replay(self, item_id: str) -> Optional[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
根据商品ID获取商品回复信息,并返回统一格式
|
||||||
|
|
||||||
|
Args:
|
||||||
|
item_id (str): 商品ID
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Optional[Dict[str, Any]]: 商品回复信息字典(统一格式),找不到返回 None
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
with self.lock:
|
||||||
|
cursor = self.conn.cursor()
|
||||||
|
cursor.execute('''
|
||||||
|
SELECT reply_content FROM item_replay
|
||||||
|
WHERE item_id = ?
|
||||||
|
''', (item_id,))
|
||||||
|
|
||||||
|
row = cursor.fetchone()
|
||||||
|
if row:
|
||||||
|
(reply_content,) = row
|
||||||
|
return {
|
||||||
|
'reply_content': reply_content or ''
|
||||||
|
}
|
||||||
|
return None
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"获取商品回复失败: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_item_reply(self, cookie_id: str, item_id: str) -> Optional[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
获取指定账号和商品的回复内容
|
||||||
|
|
||||||
|
Args:
|
||||||
|
cookie_id (str): 账号ID
|
||||||
|
item_id (str): 商品ID
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict: 包含回复内容的字典,如果不存在返回None
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
with self.lock:
|
||||||
|
cursor = self.conn.cursor()
|
||||||
|
cursor.execute('''
|
||||||
|
SELECT reply_content, created_at, updated_at
|
||||||
|
FROM item_replay
|
||||||
|
WHERE cookie_id = ? AND item_id = ?
|
||||||
|
''', (cookie_id, item_id))
|
||||||
|
|
||||||
|
row = cursor.fetchone()
|
||||||
|
if row:
|
||||||
|
return {
|
||||||
|
'reply_content': row[0] or '',
|
||||||
|
'created_at': row[1],
|
||||||
|
'updated_at': row[2]
|
||||||
|
}
|
||||||
|
return None
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"获取指定商品回复失败: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def update_item_reply(self, cookie_id: str, item_id: str, reply_content: str) -> bool:
|
||||||
|
"""
|
||||||
|
更新指定cookie和item的回复内容及更新时间
|
||||||
|
|
||||||
|
Args:
|
||||||
|
cookie_id (str): 账号ID
|
||||||
|
item_id (str): 商品ID
|
||||||
|
reply_content (str): 回复内容
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: 更新成功返回True,失败返回False
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
with self.lock:
|
||||||
|
cursor = self.conn.cursor()
|
||||||
|
cursor.execute('''
|
||||||
|
UPDATE item_replay
|
||||||
|
SET reply_content = ?, updated_at = CURRENT_TIMESTAMP
|
||||||
|
WHERE cookie_id = ? AND item_id = ?
|
||||||
|
''', (reply_content, cookie_id, item_id))
|
||||||
|
|
||||||
|
if cursor.rowcount == 0:
|
||||||
|
# 如果没更新到,说明该条记录不存在,可以考虑插入
|
||||||
|
cursor.execute('''
|
||||||
|
INSERT INTO item_replay (item_id, cookie_id, reply_content, created_at, updated_at)
|
||||||
|
VALUES (?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
|
||||||
|
''', (item_id, cookie_id, reply_content))
|
||||||
|
|
||||||
|
self.conn.commit()
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"更新商品回复失败: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_itemReplays_by_cookie(self, cookie_id: str) -> List[Dict]:
|
||||||
|
"""获取指定Cookie的所有商品信息
|
||||||
|
|
||||||
|
Args:
|
||||||
|
cookie_id: Cookie ID
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[Dict]: 商品信息列表
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
with self.lock:
|
||||||
|
cursor = self.conn.cursor()
|
||||||
|
cursor.execute('''
|
||||||
|
SELECT r.item_id, r.cookie_id, r.reply_content, r.created_at, r.updated_at, i.item_title, i.item_detail
|
||||||
|
FROM item_replay r
|
||||||
|
LEFT JOIN item_info i ON i.item_id = r.item_id
|
||||||
|
WHERE r.cookie_id = ?
|
||||||
|
ORDER BY r.updated_at DESC
|
||||||
|
''', (cookie_id,))
|
||||||
|
|
||||||
|
columns = [description[0] for description in cursor.description]
|
||||||
|
items = []
|
||||||
|
|
||||||
|
for row in cursor.fetchall():
|
||||||
|
item_info = dict(zip(columns, row))
|
||||||
|
|
||||||
|
items.append(item_info)
|
||||||
|
|
||||||
|
return items
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"获取Cookie商品信息失败: {e}")
|
||||||
|
return []
|
||||||
|
|
||||||
|
def delete_item_reply(self, cookie_id: str, item_id: str) -> bool:
|
||||||
|
"""
|
||||||
|
删除指定 cookie_id 和 item_id 的商品回复
|
||||||
|
|
||||||
|
Args:
|
||||||
|
cookie_id: Cookie ID
|
||||||
|
item_id: 商品ID
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: 删除成功返回 True,失败返回 False
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
with self.lock:
|
||||||
|
cursor = self.conn.cursor()
|
||||||
|
cursor.execute('''
|
||||||
|
DELETE FROM item_replay
|
||||||
|
WHERE cookie_id = ? AND item_id = ?
|
||||||
|
''', (cookie_id, item_id))
|
||||||
|
self.conn.commit()
|
||||||
|
# 判断是否有删除行
|
||||||
|
return cursor.rowcount > 0
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"删除商品回复失败: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def batch_delete_item_replies(self, items: List[Dict[str, str]]) -> Dict[str, int]:
|
||||||
|
"""
|
||||||
|
批量删除商品回复
|
||||||
|
|
||||||
|
Args:
|
||||||
|
items: List[Dict] 每个字典包含 cookie_id 和 item_id
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict[str, int]: 返回成功和失败的数量,例如 {"success_count": 3, "failed_count": 1}
|
||||||
|
"""
|
||||||
|
success_count = 0
|
||||||
|
failed_count = 0
|
||||||
|
|
||||||
|
try:
|
||||||
|
with self.lock:
|
||||||
|
cursor = self.conn.cursor()
|
||||||
|
for item in items:
|
||||||
|
cookie_id = item.get('cookie_id')
|
||||||
|
item_id = item.get('item_id')
|
||||||
|
if not cookie_id or not item_id:
|
||||||
|
failed_count += 1
|
||||||
|
continue
|
||||||
|
cursor.execute('''
|
||||||
|
DELETE FROM item_replay
|
||||||
|
WHERE cookie_id = ? AND item_id = ?
|
||||||
|
''', (cookie_id, item_id))
|
||||||
|
if cursor.rowcount > 0:
|
||||||
|
success_count += 1
|
||||||
|
else:
|
||||||
|
failed_count += 1
|
||||||
|
self.conn.commit()
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"批量删除商品回复失败: {e}")
|
||||||
|
# 整体失败则视为全部失败
|
||||||
|
return {"success_count": 0, "failed_count": len(items)}
|
||||||
|
|
||||||
|
return {"success_count": success_count, "failed_count": failed_count}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# 全局单例
|
# 全局单例
|
||||||
|
154
reply_server.py
154
reply_server.py
@ -3458,6 +3458,156 @@ def get_system_stats(admin_user: Dict[str, Any] = Depends(require_admin)):
|
|||||||
log_with_user('error', f"获取系统统计信息失败: {str(e)}", admin_user)
|
log_with_user('error', f"获取系统统计信息失败: {str(e)}", admin_user)
|
||||||
raise HTTPException(status_code=500, detail=str(e))
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
||||||
|
# ------------------------- 指定商品回复接口 -------------------------
|
||||||
|
|
||||||
|
@app.get("/itemReplays")
|
||||||
|
def get_all_items(current_user: Dict[str, Any] = Depends(get_current_user)):
|
||||||
|
"""获取当前用户的所有商品回复信息"""
|
||||||
|
try:
|
||||||
|
# 只返回当前用户的商品信息
|
||||||
|
user_id = current_user['user_id']
|
||||||
|
from db_manager import db_manager
|
||||||
|
user_cookies = db_manager.get_all_cookies(user_id)
|
||||||
|
|
||||||
|
all_items = []
|
||||||
|
for cookie_id in user_cookies.keys():
|
||||||
|
items = db_manager.get_itemReplays_by_cookie(cookie_id)
|
||||||
|
all_items.extend(items)
|
||||||
|
|
||||||
|
return {"items": all_items}
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=500, detail=f"获取商品回复信息失败: {str(e)}")
|
||||||
|
|
||||||
|
@app.get("/itemReplays/cookie/{cookie_id}")
|
||||||
|
def get_items_by_cookie(cookie_id: str, current_user: Dict[str, Any] = Depends(get_current_user)):
|
||||||
|
"""获取指定Cookie的商品信息"""
|
||||||
|
try:
|
||||||
|
# 检查cookie是否属于当前用户
|
||||||
|
user_id = current_user['user_id']
|
||||||
|
from db_manager import db_manager
|
||||||
|
user_cookies = db_manager.get_all_cookies(user_id)
|
||||||
|
|
||||||
|
if cookie_id not in user_cookies:
|
||||||
|
raise HTTPException(status_code=403, detail="无权限访问该Cookie")
|
||||||
|
|
||||||
|
items = db_manager.get_itemReplays_by_cookie(cookie_id)
|
||||||
|
return {"items": items}
|
||||||
|
except HTTPException:
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=500, detail=f"获取商品信息失败: {str(e)}")
|
||||||
|
|
||||||
|
@app.put("/item-reply/{cookie_id}/{item_id}")
|
||||||
|
def update_item_reply(
|
||||||
|
cookie_id: str,
|
||||||
|
item_id: str,
|
||||||
|
data: dict,
|
||||||
|
current_user: Dict[str, Any] = Depends(get_current_user)
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
更新指定账号和商品的回复内容
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
user_id = current_user['user_id']
|
||||||
|
from db_manager import db_manager
|
||||||
|
|
||||||
|
# 验证cookie是否属于用户
|
||||||
|
user_cookies = db_manager.get_all_cookies(user_id)
|
||||||
|
if cookie_id not in user_cookies:
|
||||||
|
raise HTTPException(status_code=403, detail="无权限访问该Cookie")
|
||||||
|
|
||||||
|
reply_content = data.get("reply_content", "").strip()
|
||||||
|
if not reply_content:
|
||||||
|
raise HTTPException(status_code=400, detail="回复内容不能为空")
|
||||||
|
|
||||||
|
db_manager.update_item_reply(cookie_id=cookie_id, item_id=item_id, reply_content=reply_content)
|
||||||
|
|
||||||
|
return {"message": "商品回复更新成功"}
|
||||||
|
|
||||||
|
except HTTPException:
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=500, detail=f"更新商品回复失败: {str(e)}")
|
||||||
|
|
||||||
|
@app.delete("/item-reply/{cookie_id}/{item_id}")
|
||||||
|
def delete_item_reply(cookie_id: str, item_id: str, current_user: Dict[str, Any] = Depends(get_current_user)):
|
||||||
|
"""
|
||||||
|
删除指定账号cookie_id和商品item_id的商品回复
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
user_id = current_user['user_id']
|
||||||
|
user_cookies = db_manager.get_all_cookies(user_id)
|
||||||
|
if cookie_id not in user_cookies:
|
||||||
|
raise HTTPException(status_code=403, detail="无权限访问该Cookie")
|
||||||
|
|
||||||
|
success = db_manager.delete_item_reply(cookie_id, item_id)
|
||||||
|
if not success:
|
||||||
|
raise HTTPException(status_code=404, detail="商品回复不存在")
|
||||||
|
|
||||||
|
return {"message": "商品回复删除成功"}
|
||||||
|
|
||||||
|
except HTTPException:
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=500, detail=f"删除商品回复失败: {str(e)}")
|
||||||
|
|
||||||
|
class ItemToDelete(BaseModel):
|
||||||
|
cookie_id: str
|
||||||
|
item_id: str
|
||||||
|
|
||||||
|
class BatchDeleteRequest(BaseModel):
|
||||||
|
items: List[ItemToDelete]
|
||||||
|
|
||||||
|
@app.delete("/item-reply/batch")
|
||||||
|
async def batch_delete_item_reply(
|
||||||
|
req: BatchDeleteRequest,
|
||||||
|
current_user: Dict[str, Any] = Depends(get_current_user)
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
批量删除商品回复
|
||||||
|
"""
|
||||||
|
user_id = current_user['user_id']
|
||||||
|
from db_manager import db_manager
|
||||||
|
|
||||||
|
# 先校验当前用户是否有权限删除每个cookie对应的回复
|
||||||
|
user_cookies = db_manager.get_all_cookies(user_id)
|
||||||
|
for item in req.items:
|
||||||
|
if item.cookie_id not in user_cookies:
|
||||||
|
raise HTTPException(status_code=403, detail=f"无权限访问Cookie {item.cookie_id}")
|
||||||
|
|
||||||
|
result = db_manager.batch_delete_item_replies([item.dict() for item in req.items])
|
||||||
|
return {
|
||||||
|
"success_count": result["success_count"],
|
||||||
|
"failed_count": result["failed_count"]
|
||||||
|
}
|
||||||
|
|
||||||
|
@app.get("/item-reply/{cookie_id}/{item_id}")
|
||||||
|
def get_item_reply(cookie_id: str, item_id: str, current_user: Dict[str, Any] = Depends(get_current_user)):
|
||||||
|
"""
|
||||||
|
获取指定账号cookie_id和商品item_id的商品回复内容
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
user_id = current_user['user_id']
|
||||||
|
# 校验cookie_id是否属于当前用户
|
||||||
|
user_cookies = db_manager.get_all_cookies(user_id)
|
||||||
|
if cookie_id not in user_cookies:
|
||||||
|
raise HTTPException(status_code=403, detail="无权限访问该Cookie")
|
||||||
|
|
||||||
|
# 获取指定商品回复
|
||||||
|
item_replies = db_manager.get_itemReplays_by_cookie(cookie_id)
|
||||||
|
# 找对应item_id的回复
|
||||||
|
item_reply = next((r for r in item_replies if r['item_id'] == item_id), None)
|
||||||
|
|
||||||
|
if item_reply is None:
|
||||||
|
raise HTTPException(status_code=404, detail="商品回复不存在")
|
||||||
|
|
||||||
|
return item_reply
|
||||||
|
|
||||||
|
except HTTPException:
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=500, detail=f"获取商品回复失败: {str(e)}")
|
||||||
|
|
||||||
|
|
||||||
# ------------------------- 数据库备份和恢复接口 -------------------------
|
# ------------------------- 数据库备份和恢复接口 -------------------------
|
||||||
|
|
||||||
@ -3664,7 +3814,7 @@ def get_table_data(table_name: str, admin_user: Dict[str, Any] = Depends(require
|
|||||||
'users', 'cookies', 'cookie_status', 'keywords', 'default_replies', 'default_reply_records',
|
'users', 'cookies', 'cookie_status', 'keywords', 'default_replies', 'default_reply_records',
|
||||||
'ai_reply_settings', 'ai_conversations', 'ai_item_cache', 'item_info',
|
'ai_reply_settings', 'ai_conversations', 'ai_item_cache', 'item_info',
|
||||||
'message_notifications', 'cards', 'delivery_rules', 'notification_channels',
|
'message_notifications', 'cards', 'delivery_rules', 'notification_channels',
|
||||||
'user_settings', 'system_settings', 'email_verifications', 'captcha_codes', 'orders'
|
'user_settings', 'system_settings', 'email_verifications', 'captcha_codes', 'orders', "item_replay"
|
||||||
]
|
]
|
||||||
|
|
||||||
if table_name not in allowed_tables:
|
if table_name not in allowed_tables:
|
||||||
@ -3741,7 +3891,7 @@ def clear_table_data(table_name: str, admin_user: Dict[str, Any] = Depends(requi
|
|||||||
'cookies', 'cookie_status', 'keywords', 'default_replies', 'default_reply_records',
|
'cookies', 'cookie_status', 'keywords', 'default_replies', 'default_reply_records',
|
||||||
'ai_reply_settings', 'ai_conversations', 'ai_item_cache', 'item_info',
|
'ai_reply_settings', 'ai_conversations', 'ai_item_cache', 'item_info',
|
||||||
'message_notifications', 'cards', 'delivery_rules', 'notification_channels',
|
'message_notifications', 'cards', 'delivery_rules', 'notification_channels',
|
||||||
'user_settings', 'system_settings', 'email_verifications', 'captcha_codes', 'orders'
|
'user_settings', 'system_settings', 'email_verifications', 'captcha_codes', 'orders', "item_replay"
|
||||||
]
|
]
|
||||||
|
|
||||||
# 不允许清空用户表
|
# 不允许清空用户表
|
||||||
|
@ -98,6 +98,7 @@
|
|||||||
<option value="cookie_status">cookie_status - Cookie状态表</option>
|
<option value="cookie_status">cookie_status - Cookie状态表</option>
|
||||||
<option value="keywords">keywords - 关键字表</option>
|
<option value="keywords">keywords - 关键字表</option>
|
||||||
<option value="default_replies">default_replies - 默认回复表</option>
|
<option value="default_replies">default_replies - 默认回复表</option>
|
||||||
|
<option value="item_replay">item_replay - 指定商品回复表</option>
|
||||||
<option value="default_reply_records">default_reply_records - 默认回复记录表</option>
|
<option value="default_reply_records">default_reply_records - 默认回复记录表</option>
|
||||||
<option value="ai_reply_settings">ai_reply_settings - AI回复设置表</option>
|
<option value="ai_reply_settings">ai_reply_settings - AI回复设置表</option>
|
||||||
<option value="ai_conversations">ai_conversations - AI对话历史表</option>
|
<option value="ai_conversations">ai_conversations - AI对话历史表</option>
|
||||||
@ -263,6 +264,7 @@
|
|||||||
'cookies': 'Cookie账号表',
|
'cookies': 'Cookie账号表',
|
||||||
'cookie_status': 'Cookie状态表',
|
'cookie_status': 'Cookie状态表',
|
||||||
'keywords': '关键字表',
|
'keywords': '关键字表',
|
||||||
|
'item_replay': '指定商品回复表',
|
||||||
'default_replies': '默认回复表',
|
'default_replies': '默认回复表',
|
||||||
'default_reply_records': '默认回复记录表',
|
'default_reply_records': '默认回复记录表',
|
||||||
'ai_reply_settings': 'AI回复设置表',
|
'ai_reply_settings': 'AI回复设置表',
|
||||||
|
@ -54,6 +54,12 @@
|
|||||||
自动回复
|
自动回复
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="nav-item">
|
||||||
|
<a href="#" class="nav-link" onclick="showSection('items-reply')">
|
||||||
|
<i class="bi bi-chat-left-text"></i>
|
||||||
|
指定商品回复
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
<div class="nav-item">
|
<div class="nav-item">
|
||||||
<a href="#" class="nav-link" onclick="showSection('cards')">
|
<a href="#" class="nav-link" onclick="showSection('cards')">
|
||||||
<i class="bi bi-credit-card"></i>
|
<i class="bi bi-credit-card"></i>
|
||||||
@ -397,6 +403,7 @@
|
|||||||
<th style="width: 12%">商品ID</th>
|
<th style="width: 12%">商品ID</th>
|
||||||
<th style="width: 18%">商品标题</th>
|
<th style="width: 18%">商品标题</th>
|
||||||
<th style="width: 20%">商品详情</th>
|
<th style="width: 20%">商品详情</th>
|
||||||
|
<th style="width: 20%">商品价格</th>
|
||||||
<th style="width: 8%">多规格</th>
|
<th style="width: 8%">多规格</th>
|
||||||
<th style="width: 10%">更新时间</th>
|
<th style="width: 10%">更新时间</th>
|
||||||
<th style="width: 15%">操作</th>
|
<th style="width: 15%">操作</th>
|
||||||
@ -456,6 +463,140 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 指定商品自动管理内容 -->
|
||||||
|
<div id="items-reply-section" class="content-section">
|
||||||
|
<div class="content-header">
|
||||||
|
<h2 class="mb-0">
|
||||||
|
<i class="bi bi-box-seam me-2"></i>
|
||||||
|
商品回复管理
|
||||||
|
</h2>
|
||||||
|
<p class="text-muted mb-0">管理各账号的商品信息</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content-body">
|
||||||
|
<!-- Cookie筛选 -->
|
||||||
|
<div class="card mb-4">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="row align-items-end">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label for="itemCookieFilter" class="form-label">筛选账号</label>
|
||||||
|
<select class="form-select" id="itemReplayCookieFilter" onchange="loadItemsReplayByCookie()">
|
||||||
|
<option value="">所有账号</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="d-flex justify-content-end align-items-end gap-2">
|
||||||
|
<!-- 页码输入 -->
|
||||||
|
<div class="d-flex align-items-center gap-2">
|
||||||
|
<label for="pageNumber" class="form-label mb-0 text-nowrap">页码:</label>
|
||||||
|
<input type="number" class="form-control" id="pageNumber" placeholder="页码" min="1" value="1" style="width: 80px;">
|
||||||
|
</div>
|
||||||
|
<div class="d-flex gap-2">
|
||||||
|
<button class="btn btn-success" onclick="showItemReplayEdit()">
|
||||||
|
添加商品回复
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-primary" onclick="refreshItemReplayS()">
|
||||||
|
<i class="bi bi-arrow-clockwise me-1"></i>刷新
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 商品列表 -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header d-flex justify-content-between align-items-center">
|
||||||
|
<h5 class="mb-0">商品列表(自动发货根据商品标题和商品详情匹配关键字)</h5>
|
||||||
|
<button class="btn btn-sm btn-outline-danger" onclick="batchDeleteItemReplies()">
|
||||||
|
<i class="bi bi-trash"></i> 批量删除
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style="width: 5%">
|
||||||
|
<input type="checkbox" id="selectAllItemReplay" onchange="toggleSelectAll(this)">
|
||||||
|
</th>
|
||||||
|
<th style="width: 12%">账号ID</th>
|
||||||
|
<th style="width: 12%">商品ID</th>
|
||||||
|
<th style="width: 18%">商品标题</th>
|
||||||
|
<th style="width: 20%">商品内容</th>
|
||||||
|
<th style="width: 18%">回复内容</th>
|
||||||
|
<th style="width: 10%">更新时间</th>
|
||||||
|
<th style="width: 15%">操作</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="itemReplaysTableBody">
|
||||||
|
<tr>
|
||||||
|
<td colspan="5" class="text-center text-muted">加载中...</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 商品回复弹框 -->
|
||||||
|
<!-- 添加/编辑商品回复模态框 -->
|
||||||
|
<div class="modal fade" id="editItemReplyModal" tabindex="-1">
|
||||||
|
<div class="modal-dialog modal-xl">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">
|
||||||
|
<i class="bi bi-chat-text me-2"></i><span id="itemReplayTitle">编辑商品回复</span>
|
||||||
|
</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<form id="editItemReplyForm">
|
||||||
|
<input type="hidden" id="editReplyCookieId">
|
||||||
|
<input type="hidden" id="editReplyItemId">
|
||||||
|
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label">账号ID<span class="text-danger">*</span></label>
|
||||||
|
<select id="editReplyCookieIdSelect" class="form-select" onchange="onCookieChangeForReply()">
|
||||||
|
<option value="">请选择账号</option>
|
||||||
|
<!-- JS 动态填充账号选项 -->
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label">商品ID<span class="text-danger">*</span></label>
|
||||||
|
<select id="editReplyItemIdSelect" class="form-select">
|
||||||
|
<option value="">选择商品</option>
|
||||||
|
<!-- JS 动态填充商品选项 -->
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="editReplyContent" class="form-label">商品回复内容 <span class="text-danger">*</span></label>
|
||||||
|
<textarea class="form-control" id="editItemReplyContent" rows="10"
|
||||||
|
placeholder="请输入商品回复内容..."></textarea>
|
||||||
|
<div class="form-text">
|
||||||
|
<i class="bi bi-info-circle me-1"></i>
|
||||||
|
请输入商品的自动回复内容,用户购买后将收到该回复。
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
|
||||||
|
<button type="button" class="btn btn-primary" onclick="saveItemReply()">
|
||||||
|
<i class="bi bi-check-circle me-1"></i>保存
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<!-- 订单管理内容 -->
|
<!-- 订单管理内容 -->
|
||||||
<div id="orders-section" class="content-section">
|
<div id="orders-section" class="content-section">
|
||||||
<div class="content-header">
|
<div class="content-header">
|
||||||
|
458
static/js/app.js
458
static/js/app.js
@ -77,6 +77,9 @@ function showSection(sectionName) {
|
|||||||
case 'items': // 【商品管理菜单】
|
case 'items': // 【商品管理菜单】
|
||||||
loadItems();
|
loadItems();
|
||||||
break;
|
break;
|
||||||
|
case 'items-reply': // 【商品回复管理菜单】
|
||||||
|
loadItemsReplay();
|
||||||
|
break;
|
||||||
case 'orders': // 【订单管理菜单】
|
case 'orders': // 【订单管理菜单】
|
||||||
loadOrders();
|
loadOrders();
|
||||||
break;
|
break;
|
||||||
@ -4877,7 +4880,7 @@ async function toggleItemMultiSpec(cookieId, itemId, isMultiSpec) {
|
|||||||
async function loadItems() {
|
async function loadItems() {
|
||||||
try {
|
try {
|
||||||
// 先加载Cookie列表用于筛选
|
// 先加载Cookie列表用于筛选
|
||||||
await loadCookieFilter();
|
await loadCookieFilter('itemCookieFilter');
|
||||||
|
|
||||||
// 加载商品列表
|
// 加载商品列表
|
||||||
await refreshItemsData();
|
await refreshItemsData();
|
||||||
@ -4903,7 +4906,7 @@ async function refreshItemsData() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 加载Cookie筛选选项
|
// 加载Cookie筛选选项
|
||||||
async function loadCookieFilter() {
|
async function loadCookieFilter(id) {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${apiBase}/cookies/details`, {
|
const response = await fetch(`${apiBase}/cookies/details`, {
|
||||||
headers: {
|
headers: {
|
||||||
@ -4913,7 +4916,7 @@ async function loadCookieFilter() {
|
|||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const accounts = await response.json();
|
const accounts = await response.json();
|
||||||
const select = document.getElementById('itemCookieFilter');
|
const select = document.getElementById(id);
|
||||||
|
|
||||||
// 保存当前选择的值
|
// 保存当前选择的值
|
||||||
const currentValue = select.value;
|
const currentValue = select.value;
|
||||||
@ -5647,6 +5650,455 @@ function escapeHtml(text) {
|
|||||||
return div.innerHTML;
|
return div.innerHTML;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ================================
|
||||||
|
// 【商品回复管理菜单】相关功能
|
||||||
|
// ================================
|
||||||
|
|
||||||
|
// 加载商品回复列表
|
||||||
|
async function loadItemsReplay() {
|
||||||
|
try {
|
||||||
|
// 先加载Cookie列表用于筛选
|
||||||
|
await loadCookieFilter('itemReplayCookieFilter');
|
||||||
|
await loadCookieFilterPlus('editReplyCookieIdSelect');
|
||||||
|
// 加载商品列表
|
||||||
|
await refreshItemsReplayData();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载商品列表失败:', error);
|
||||||
|
showToast('加载商品列表失败', 'danger');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 只刷新商品回复数据,不重新加载筛选器
|
||||||
|
async function refreshItemsReplayData() {
|
||||||
|
try {
|
||||||
|
const selectedCookie = document.getElementById('itemCookieFilter').value;
|
||||||
|
if (selectedCookie) {
|
||||||
|
await loadItemsReplayByCookie();
|
||||||
|
} else {
|
||||||
|
await loadAllItemReplays();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('刷新商品数据失败:', error);
|
||||||
|
showToast('刷新商品数据失败', 'danger');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载Cookie筛选选项添加弹框中使用
|
||||||
|
async function loadCookieFilterPlus(id) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${apiBase}/cookies/details`, {
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${authToken}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const accounts = await response.json();
|
||||||
|
const select = document.getElementById(id);
|
||||||
|
|
||||||
|
// 保存当前选择的值
|
||||||
|
const currentValue = select.value;
|
||||||
|
|
||||||
|
// 清空现有选项(保留"所有账号")
|
||||||
|
select.innerHTML = '<option value="">选择账号</option>';
|
||||||
|
|
||||||
|
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 refreshItemReplayS() {
|
||||||
|
await refreshItemsReplayData();
|
||||||
|
showToast('商品列表已刷新', 'success');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载所有商品回复
|
||||||
|
async function loadAllItemReplays() {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${apiBase}/itemReplays`, {
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${authToken}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const data = await response.json();
|
||||||
|
displayItemReplays(data.items);
|
||||||
|
} else {
|
||||||
|
throw new Error('获取商品列表失败');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载商品列表失败:', error);
|
||||||
|
showToast('加载商品列表失败', 'danger');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按Cookie加载商品回复
|
||||||
|
async function loadItemsReplayByCookie() {
|
||||||
|
const cookieId = document.getElementById('itemReplayCookieFilter').value;
|
||||||
|
if (!cookieId) {
|
||||||
|
await loadAllItemReplays();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${apiBase}/itemReplays/cookie/${encodeURIComponent(cookieId)}`, {
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${authToken}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const data = await response.json();
|
||||||
|
displayItemReplays(data.items);
|
||||||
|
} else {
|
||||||
|
throw new Error('获取商品列表失败');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载商品列表失败:', error);
|
||||||
|
showToast('加载商品列表失败', 'danger');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示商品回复列表
|
||||||
|
function displayItemReplays(items) {
|
||||||
|
const tbody = document.getElementById('itemReplaysTableBody');
|
||||||
|
|
||||||
|
if (!items || items.length === 0) {
|
||||||
|
tbody.innerHTML = '<tr><td colspan="5" class="text-center text-muted">暂无商品数据</td></tr>';
|
||||||
|
// 重置选择状态
|
||||||
|
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 ? '...' : '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return `
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<input type="checkbox" name="itemCheckbox"
|
||||||
|
data-cookie-id="${escapeHtml(item.cookie_id)}"
|
||||||
|
data-item-id="${escapeHtml(item.item_id)}"
|
||||||
|
onchange="updateSelectAllState()">
|
||||||
|
</td>
|
||||||
|
<td>${escapeHtml(item.cookie_id)}</td>
|
||||||
|
<td>${escapeHtml(item.item_id)}</td>
|
||||||
|
<td title="${escapeHtml(item.item_title || '未设置')}">${escapeHtml(itemTitleDisplay)}</td>
|
||||||
|
<td title="${escapeHtml(item.item_detail || '未设置')}">${escapeHtml(itemDetailDisplay)}</td>
|
||||||
|
<td title="${escapeHtml(item.reply_content || '未设置')}">${escapeHtml(item.reply_content)}</td>
|
||||||
|
<td>${formatDateTime(item.updated_at)}</td>
|
||||||
|
<td>
|
||||||
|
<div class="btn-group" role="group">
|
||||||
|
<button class="btn btn-sm btn-outline-primary" onclick="editItemReply('${escapeHtml(item.cookie_id)}', '${escapeHtml(item.item_id)}')" title="编辑详情">
|
||||||
|
<i class="bi bi-pencil"></i>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-sm btn-outline-danger" onclick="deleteItemReply('${escapeHtml(item.cookie_id)}', '${escapeHtml(item.item_id)}', '${escapeHtml(item.item_title || item.item_id)}')" title="删除">
|
||||||
|
<i class="bi bi-trash"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
`;
|
||||||
|
}).join('');
|
||||||
|
|
||||||
|
// 更新表格内容
|
||||||
|
tbody.innerHTML = itemsHtml;
|
||||||
|
|
||||||
|
// 重置选择状态
|
||||||
|
const selectAllCheckbox = document.getElementById('selectAllItems');
|
||||||
|
if (selectAllCheckbox) {
|
||||||
|
selectAllCheckbox.checked = false;
|
||||||
|
selectAllCheckbox.indeterminate = false;
|
||||||
|
}
|
||||||
|
updateBatchDeleteButton();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示添加弹框
|
||||||
|
async function showItemReplayEdit(){
|
||||||
|
// 显示模态框
|
||||||
|
const modal = new bootstrap.Modal(document.getElementById('editItemReplyModal'));
|
||||||
|
document.getElementById('editReplyCookieIdSelect').value = '';
|
||||||
|
document.getElementById('editReplyItemIdSelect').value = '';
|
||||||
|
document.getElementById('editReplyItemIdSelect').disabled = true
|
||||||
|
document.getElementById('editItemReplyContent').value = '';
|
||||||
|
document.getElementById('itemReplayTitle').textContent = '添加商品回复';
|
||||||
|
modal.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 当账号变化时加载对应商品
|
||||||
|
async function onCookieChangeForReply() {
|
||||||
|
const cookieId = document.getElementById('editReplyCookieIdSelect').value;
|
||||||
|
const itemSelect = document.getElementById('editReplyItemIdSelect');
|
||||||
|
|
||||||
|
itemSelect.innerHTML = '<option value="">选择商品</option>';
|
||||||
|
if (!cookieId) {
|
||||||
|
itemSelect.disabled = true; // 禁用选择框
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
itemSelect.disabled = false; // 启用选择框
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch(`${apiBase}/items/cookie/${encodeURIComponent(cookieId)}`, {
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${authToken}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
if (response.ok) {
|
||||||
|
const data = await response.json();
|
||||||
|
data.items.forEach(item => {
|
||||||
|
const opt = document.createElement('option');
|
||||||
|
opt.value = item.item_id;
|
||||||
|
opt.textContent = `${item.item_id} - ${item.item_title || '无标题'}`;
|
||||||
|
itemSelect.appendChild(opt);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
throw new Error('获取商品列表失败');
|
||||||
|
}
|
||||||
|
}catch (error) {
|
||||||
|
console.error('加载商品列表失败:', error);
|
||||||
|
showToast('加载商品列表失败', 'danger');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 编辑商品回复
|
||||||
|
async function editItemReply(cookieId, itemId) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${apiBase}/item-reply/${encodeURIComponent(cookieId)}/${encodeURIComponent(itemId)}`, {
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${authToken}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (response.ok) {
|
||||||
|
const data = await response.json();
|
||||||
|
document.getElementById('itemReplayTitle').textContent = '编辑商品回复';
|
||||||
|
// 填充表单
|
||||||
|
document.getElementById('editReplyCookieIdSelect').value = data.cookie_id;
|
||||||
|
let res = await onCookieChangeForReply()
|
||||||
|
document.getElementById('editReplyItemIdSelect').value = data.item_id;
|
||||||
|
document.getElementById('editItemReplyContent').value = data.reply_content || '';
|
||||||
|
|
||||||
|
} else if (response.status === 404) {
|
||||||
|
// 如果没有记录,则填充空白内容(用于添加)
|
||||||
|
// document.getElementById('editReplyCookieIdSelect').value = data.cookie_id;
|
||||||
|
// document.getElementById('editReplyItemIdSelect').value = data.item_id;
|
||||||
|
// document.getElementById('editItemReplyContent').value = data.reply_content || '';
|
||||||
|
} else {
|
||||||
|
throw new Error('获取商品回复失败');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示模态框
|
||||||
|
const modal = new bootstrap.Modal(document.getElementById('editItemReplyModal'));
|
||||||
|
modal.show();
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取商品回复失败:', error);
|
||||||
|
showToast('获取商品回复失败', 'danger');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存商品回复
|
||||||
|
async function saveItemReply() {
|
||||||
|
const cookieId = document.getElementById('editReplyCookieIdSelect').value;
|
||||||
|
const itemId = document.getElementById('editReplyItemIdSelect').value;
|
||||||
|
const replyContent = document.getElementById('editItemReplyContent').value.trim();
|
||||||
|
|
||||||
|
console.log(cookieId)
|
||||||
|
console.log(itemId)
|
||||||
|
console.log(replyContent)
|
||||||
|
if (!cookieId) {
|
||||||
|
showToast('请选择账号', 'warning');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!itemId) {
|
||||||
|
showToast('请选择商品', 'warning');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!replyContent) {
|
||||||
|
showToast('请输入商品回复内容', 'warning');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${apiBase}/item-reply/${encodeURIComponent(cookieId)}/${encodeURIComponent(itemId)}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': `Bearer ${authToken}`
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
reply_content: replyContent
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
showToast('商品回复保存成功', 'success');
|
||||||
|
|
||||||
|
// 关闭模态框
|
||||||
|
const modal = bootstrap.Modal.getInstance(document.getElementById('editItemReplyModal'));
|
||||||
|
modal.hide();
|
||||||
|
|
||||||
|
// 可选:刷新数据
|
||||||
|
await refreshItemsReplayData?.();
|
||||||
|
} else {
|
||||||
|
const error = await response.text();
|
||||||
|
showToast(`保存失败: ${error}`, 'danger');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('保存商品回复失败:', error);
|
||||||
|
showToast('保存商品回复失败', 'danger');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除商品回复
|
||||||
|
async function deleteItemReply(cookieId, itemId, itemTitle) {
|
||||||
|
try {
|
||||||
|
const confirmed = confirm(`确定要删除该商品的自动回复吗?\n\n商品ID: ${itemId}\n商品标题: ${itemTitle || '未设置'}\n\n此操作不可撤销!`);
|
||||||
|
if (!confirmed) return;
|
||||||
|
|
||||||
|
const response = await fetch(`${apiBase}/item-reply/${encodeURIComponent(cookieId)}/${encodeURIComponent(itemId)}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${authToken}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
showToast('商品回复删除成功', 'success');
|
||||||
|
await loadItemsReplayByCookie?.(); // 如果你有刷新商品列表的函数
|
||||||
|
} else {
|
||||||
|
const error = await response.text();
|
||||||
|
showToast(`删除失败: ${error}`, 'danger');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('删除商品回复失败:', error);
|
||||||
|
showToast('删除商品回复失败', 'danger');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量删除商品回复
|
||||||
|
async function batchDeleteItemReplies() {
|
||||||
|
try {
|
||||||
|
const checkboxes = document.querySelectorAll('input[name="itemCheckbox"]:checked');
|
||||||
|
if (checkboxes.length === 0) {
|
||||||
|
showToast('请选择要删除回复的商品', 'warning');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const confirmed = confirm(`确定要删除选中商品的自动回复吗?\n共 ${checkboxes.length} 个商品\n\n此操作不可撤销!`);
|
||||||
|
if (!confirmed) return;
|
||||||
|
|
||||||
|
const itemsToDelete = Array.from(checkboxes).map(checkbox => ({
|
||||||
|
cookie_id: checkbox.dataset.cookieId,
|
||||||
|
item_id: checkbox.dataset.itemId
|
||||||
|
}));
|
||||||
|
|
||||||
|
const response = await fetch(`${apiBase}/item-reply/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 loadItemsReplayByCookie?.();
|
||||||
|
} else {
|
||||||
|
const error = await response.text();
|
||||||
|
showToast(`批量删除失败: ${error}`, 'danger');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('批量删除商品回复失败:', error);
|
||||||
|
showToast('批量删除商品回复失败', 'danger');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ================================
|
// ================================
|
||||||
// 【日志管理菜单】相关功能
|
// 【日志管理菜单】相关功能
|
||||||
// ================================
|
// ================================
|
||||||
|
Loading…
x
Reference in New Issue
Block a user