mirror of
https://github.com/zhinianboke/xianyu-auto-reply.git
synced 2025-08-02 20:47:35 +08:00
支持多规格发货
This commit is contained in:
parent
81b1f924cc
commit
1dd4d9b841
@ -32,10 +32,12 @@
|
|||||||
|
|
||||||
### 🚚 自动发货功能
|
### 🚚 自动发货功能
|
||||||
- **智能匹配** - 基于商品信息自动匹配发货规则
|
- **智能匹配** - 基于商品信息自动匹配发货规则
|
||||||
|
- **多规格支持** - 支持同一商品的不同规格自动匹配对应卡券
|
||||||
|
- **精确匹配+兜底机制** - 优先精确匹配规格,失败时自动降级到普通卡券
|
||||||
- **延时发货** - 支持设置发货延时时间(0-3600秒)
|
- **延时发货** - 支持设置发货延时时间(0-3600秒)
|
||||||
- **多种触发** - 支持付款消息、小刀消息等多种触发条件
|
- **多种触发** - 支持付款消息、小刀消息等多种触发条件
|
||||||
- **防重复发货** - 智能防重复机制,避免重复发货
|
- **防重复发货** - 智能防重复机制,避免重复发货
|
||||||
- **多种发货方式** - 支持文本内容、卡密文件、API调用等发货方式
|
- **多种发货方式** - 支持固定文字、批量数据、API调用等发货方式
|
||||||
- **自动确认发货** - 检测到付款后自动调用闲鱼API确认发货
|
- **自动确认发货** - 检测到付款后自动调用闲鱼API确认发货
|
||||||
- **防重复确认** - 智能防重复确认机制,避免重复API调用
|
- **防重复确认** - 智能防重复确认机制,避免重复API调用
|
||||||
- **发货统计** - 完整的发货记录和统计功能
|
- **发货统计** - 完整的发货记录和统计功能
|
||||||
@ -43,7 +45,8 @@
|
|||||||
### 🛍️ 商品管理
|
### 🛍️ 商品管理
|
||||||
- **自动收集** - 消息触发时自动收集商品信息
|
- **自动收集** - 消息触发时自动收集商品信息
|
||||||
- **API获取** - 通过闲鱼API获取完整商品详情
|
- **API获取** - 通过闲鱼API获取完整商品详情
|
||||||
- **批量管理** - 支持批量查看、编辑商品信息
|
- **多规格支持** - 支持多规格商品的规格信息管理
|
||||||
|
- **批量管理** - 支持批量查看、编辑、切换多规格状态
|
||||||
- **智能去重** - 自动去重,避免重复存储
|
- **智能去重** - 自动去重,避免重复存储
|
||||||
|
|
||||||
### 🔍 商品搜索功能
|
### 🔍 商品搜索功能
|
||||||
@ -63,6 +66,8 @@
|
|||||||
- **模板生成** - 自动生成包含示例数据的导入模板
|
- **模板生成** - 自动生成包含示例数据的导入模板
|
||||||
- **批量操作** - 支持批量添加、更新关键词数据
|
- **批量操作** - 支持批量添加、更新关键词数据
|
||||||
- **数据验证** - 导入时自动验证数据格式和重复性
|
- **数据验证** - 导入时自动验证数据格式和重复性
|
||||||
|
- **多规格卡券管理** - 支持创建和管理多规格卡券
|
||||||
|
- **发货规则管理** - 支持多规格发货规则的创建和管理
|
||||||
- **数据备份** - 自动数据备份和恢复
|
- **数据备份** - 自动数据备份和恢复
|
||||||
|
|
||||||
## 📁 项目结构
|
## 📁 项目结构
|
||||||
|
@ -1103,7 +1103,7 @@ class XianyuLive:
|
|||||||
from utils.order_detail_fetcher import fetch_order_detail_simple
|
from utils.order_detail_fetcher import fetch_order_detail_simple
|
||||||
|
|
||||||
# 获取当前账号的cookie字符串
|
# 获取当前账号的cookie字符串
|
||||||
cookie_string = self.cookie_value
|
cookie_string = self.cookies_str
|
||||||
logger.debug(f"【{self.cookie_id}】使用Cookie长度: {len(cookie_string) if cookie_string else 0}")
|
logger.debug(f"【{self.cookie_id}】使用Cookie长度: {len(cookie_string) if cookie_string else 0}")
|
||||||
|
|
||||||
# 异步获取订单详情(使用当前账号的cookie和无头模式)
|
# 异步获取订单详情(使用当前账号的cookie和无头模式)
|
||||||
@ -1236,19 +1236,74 @@ class XianyuLive:
|
|||||||
|
|
||||||
logger.info(f"使用搜索文本匹配发货规则: {search_text[:100]}...")
|
logger.info(f"使用搜索文本匹配发货规则: {search_text[:100]}...")
|
||||||
|
|
||||||
# 根据商品信息查找匹配的发货规则
|
# 检查商品是否为多规格商品
|
||||||
|
is_multi_spec = db_manager.get_item_multi_spec_status(self.cookie_id, item_id)
|
||||||
|
spec_name = None
|
||||||
|
spec_value = None
|
||||||
|
|
||||||
|
# 如果是多规格商品且有订单ID,获取规格信息
|
||||||
|
if is_multi_spec and order_id:
|
||||||
|
logger.info(f"检测到多规格商品,获取订单规格信息: {order_id}")
|
||||||
|
try:
|
||||||
|
order_detail = await self.fetch_order_detail_info(order_id)
|
||||||
|
if order_detail:
|
||||||
|
spec_name = order_detail.get('spec_name', '')
|
||||||
|
spec_value = order_detail.get('spec_value', '')
|
||||||
|
if spec_name and spec_value:
|
||||||
|
logger.info(f"获取到规格信息: {spec_name} = {spec_value}")
|
||||||
|
else:
|
||||||
|
logger.warning(f"未能获取到规格信息,将使用兜底匹配")
|
||||||
|
else:
|
||||||
|
logger.warning(f"获取订单详情失败,将使用兜底匹配")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"获取订单规格信息失败: {self._safe_str(e)},将使用兜底匹配")
|
||||||
|
|
||||||
|
# 智能匹配发货规则:优先精确匹配,然后兜底匹配
|
||||||
|
delivery_rules = []
|
||||||
|
|
||||||
|
# 第一步:如果有规格信息,尝试精确匹配多规格发货规则
|
||||||
|
if spec_name and spec_value:
|
||||||
|
logger.info(f"尝试精确匹配多规格发货规则: {search_text[:50]}... [{spec_name}:{spec_value}]")
|
||||||
|
delivery_rules = db_manager.get_delivery_rules_by_keyword_and_spec(search_text, spec_name, spec_value)
|
||||||
|
|
||||||
|
if delivery_rules:
|
||||||
|
logger.info(f"✅ 找到精确匹配的多规格发货规则: {len(delivery_rules)}个")
|
||||||
|
else:
|
||||||
|
logger.info(f"❌ 未找到精确匹配的多规格发货规则")
|
||||||
|
|
||||||
|
# 第二步:如果精确匹配失败,尝试兜底匹配(普通发货规则)
|
||||||
|
if not delivery_rules:
|
||||||
|
logger.info(f"尝试兜底匹配普通发货规则: {search_text[:50]}...")
|
||||||
delivery_rules = db_manager.get_delivery_rules_by_keyword(search_text)
|
delivery_rules = db_manager.get_delivery_rules_by_keyword(search_text)
|
||||||
|
|
||||||
|
if delivery_rules:
|
||||||
|
logger.info(f"✅ 找到兜底匹配的普通发货规则: {len(delivery_rules)}个")
|
||||||
|
else:
|
||||||
|
logger.info(f"❌ 未找到任何匹配的发货规则")
|
||||||
|
|
||||||
if not delivery_rules:
|
if not delivery_rules:
|
||||||
logger.info(f"未找到匹配的发货规则: {search_text[:50]}...")
|
logger.warning(f"未找到匹配的发货规则: {search_text[:50]}...")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# 使用第一个匹配的规则(按关键字长度降序排列,优先匹配更精确的规则)
|
# 使用第一个匹配的规则(按关键字长度降序排列,优先匹配更精确的规则)
|
||||||
|
rule = delivery_rules[0]
|
||||||
|
|
||||||
# 保存商品信息到数据库
|
# 保存商品信息到数据库
|
||||||
await self.save_item_info_to_db(item_id, search_text)
|
await self.save_item_info_to_db(item_id, search_text)
|
||||||
rule = delivery_rules[0]
|
|
||||||
logger.info(f"找到匹配的发货规则: {rule['keyword']} -> {rule['card_name']} ({rule['card_type']})")
|
# 详细的匹配结果日志
|
||||||
|
if rule.get('is_multi_spec'):
|
||||||
|
if spec_name and spec_value:
|
||||||
|
logger.info(f"🎯 精确匹配多规格发货规则: {rule['keyword']} -> {rule['card_name']} [{rule['spec_name']}:{rule['spec_value']}]")
|
||||||
|
logger.info(f"📋 订单规格: {spec_name}:{spec_value} ✅ 匹配卡券规格: {rule['spec_name']}:{rule['spec_value']}")
|
||||||
|
else:
|
||||||
|
logger.info(f"⚠️ 使用多规格发货规则但无订单规格信息: {rule['keyword']} -> {rule['card_name']} [{rule['spec_name']}:{rule['spec_value']}]")
|
||||||
|
else:
|
||||||
|
if spec_name and spec_value:
|
||||||
|
logger.info(f"🔄 兜底匹配普通发货规则: {rule['keyword']} -> {rule['card_name']} ({rule['card_type']})")
|
||||||
|
logger.info(f"📋 订单规格: {spec_name}:{spec_value} ➡️ 使用普通卡券兜底")
|
||||||
|
else:
|
||||||
|
logger.info(f"✅ 匹配普通发货规则: {rule['keyword']} -> {rule['card_name']} ({rule['card_type']})")
|
||||||
|
|
||||||
# 获取延时设置
|
# 获取延时设置
|
||||||
delay_seconds = rule.get('card_delay_seconds', 0)
|
delay_seconds = rule.get('card_delay_seconds', 0)
|
||||||
@ -1297,7 +1352,7 @@ class XianyuLive:
|
|||||||
|
|
||||||
elif rule['card_type'] == 'text':
|
elif rule['card_type'] == 'text':
|
||||||
# 固定文字类型:直接使用文字内容
|
# 固定文字类型:直接使用文字内容
|
||||||
delivery_content = rule['card_text_content']
|
delivery_content = rule['text_content']
|
||||||
|
|
||||||
elif rule['card_type'] == 'data':
|
elif rule['card_type'] == 'data':
|
||||||
# 批量数据类型:获取并消费第一条数据
|
# 批量数据类型:获取并消费第一条数据
|
||||||
@ -1351,10 +1406,12 @@ class XianyuLive:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
import aiohttp
|
import aiohttp
|
||||||
|
import json
|
||||||
|
|
||||||
api_config = rule.get('card_api_config')
|
api_config = rule.get('api_config')
|
||||||
if not api_config:
|
if not api_config:
|
||||||
logger.error("API配置为空")
|
logger.error(f"API配置为空,规则ID: {rule.get('id')}, 卡券名称: {rule.get('card_name')}")
|
||||||
|
logger.debug(f"规则详情: {rule}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# 解析API配置
|
# 解析API配置
|
||||||
|
294
db_manager.py
294
db_manager.py
@ -198,6 +198,9 @@ class DBManager:
|
|||||||
description TEXT,
|
description TEXT,
|
||||||
enabled BOOLEAN DEFAULT TRUE,
|
enabled BOOLEAN DEFAULT TRUE,
|
||||||
delay_seconds INTEGER DEFAULT 0,
|
delay_seconds INTEGER DEFAULT 0,
|
||||||
|
is_multi_spec BOOLEAN DEFAULT FALSE,
|
||||||
|
spec_name TEXT,
|
||||||
|
spec_value TEXT,
|
||||||
user_id INTEGER NOT NULL DEFAULT 1,
|
user_id INTEGER NOT NULL DEFAULT 1,
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
@ -244,6 +247,7 @@ class DBManager:
|
|||||||
item_category TEXT,
|
item_category TEXT,
|
||||||
item_price TEXT,
|
item_price TEXT,
|
||||||
item_detail TEXT,
|
item_detail TEXT,
|
||||||
|
is_multi_spec BOOLEAN DEFAULT FALSE,
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
FOREIGN KEY (cookie_id) REFERENCES cookies(id) ON DELETE CASCADE,
|
FOREIGN KEY (cookie_id) REFERENCES cookies(id) ON DELETE CASCADE,
|
||||||
@ -412,6 +416,24 @@ class DBManager:
|
|||||||
# type列存在,更新NULL值
|
# type列存在,更新NULL值
|
||||||
self._execute_sql(cursor, "UPDATE email_verifications SET type = 'register' WHERE type IS NULL")
|
self._execute_sql(cursor, "UPDATE email_verifications SET type = 'register' WHERE type IS NULL")
|
||||||
|
|
||||||
|
# 为cards表添加多规格字段(如果不存在)
|
||||||
|
try:
|
||||||
|
self._execute_sql(cursor, "SELECT is_multi_spec FROM cards LIMIT 1")
|
||||||
|
except sqlite3.OperationalError:
|
||||||
|
# 多规格字段不存在,需要添加
|
||||||
|
self._execute_sql(cursor, "ALTER TABLE cards ADD COLUMN is_multi_spec BOOLEAN DEFAULT FALSE")
|
||||||
|
self._execute_sql(cursor, "ALTER TABLE cards ADD COLUMN spec_name TEXT")
|
||||||
|
self._execute_sql(cursor, "ALTER TABLE cards ADD COLUMN spec_value TEXT")
|
||||||
|
logger.info("为cards表添加多规格字段")
|
||||||
|
|
||||||
|
# 为item_info表添加多规格字段(如果不存在)
|
||||||
|
try:
|
||||||
|
self._execute_sql(cursor, "SELECT is_multi_spec FROM item_info LIMIT 1")
|
||||||
|
except sqlite3.OperationalError:
|
||||||
|
# 多规格字段不存在,需要添加
|
||||||
|
self._execute_sql(cursor, "ALTER TABLE item_info ADD COLUMN is_multi_spec BOOLEAN DEFAULT FALSE")
|
||||||
|
logger.info("为item_info表添加多规格字段")
|
||||||
|
|
||||||
self.conn.commit()
|
self.conn.commit()
|
||||||
logger.info(f"数据库初始化成功: {self.db_path}")
|
logger.info(f"数据库初始化成功: {self.db_path}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@ -1740,10 +1762,37 @@ class DBManager:
|
|||||||
|
|
||||||
def create_card(self, name: str, card_type: str, api_config=None,
|
def create_card(self, name: str, card_type: str, api_config=None,
|
||||||
text_content: str = None, data_content: str = None,
|
text_content: str = None, data_content: str = None,
|
||||||
description: str = None, enabled: bool = True, delay_seconds: int = 0, user_id: int = None):
|
description: str = None, enabled: bool = True, delay_seconds: int = 0,
|
||||||
"""创建新卡券"""
|
is_multi_spec: bool = False, spec_name: str = None, spec_value: str = None,
|
||||||
|
user_id: int = None):
|
||||||
|
"""创建新卡券(支持多规格)"""
|
||||||
with self.lock:
|
with self.lock:
|
||||||
try:
|
try:
|
||||||
|
# 验证多规格参数
|
||||||
|
if is_multi_spec:
|
||||||
|
if not spec_name or not spec_value:
|
||||||
|
raise ValueError("多规格卡券必须提供规格名称和规格值")
|
||||||
|
|
||||||
|
# 检查唯一性:卡券名称+规格名称+规格值
|
||||||
|
cursor = self.conn.cursor()
|
||||||
|
cursor.execute('''
|
||||||
|
SELECT COUNT(*) FROM cards
|
||||||
|
WHERE name = ? AND spec_name = ? AND spec_value = ? AND user_id = ?
|
||||||
|
''', (name, spec_name, spec_value, user_id))
|
||||||
|
|
||||||
|
if cursor.fetchone()[0] > 0:
|
||||||
|
raise ValueError(f"卡券已存在:{name} - {spec_name}:{spec_value}")
|
||||||
|
else:
|
||||||
|
# 检查唯一性:仅卡券名称
|
||||||
|
cursor = self.conn.cursor()
|
||||||
|
cursor.execute('''
|
||||||
|
SELECT COUNT(*) FROM cards
|
||||||
|
WHERE name = ? AND (is_multi_spec = 0 OR is_multi_spec IS NULL) AND user_id = ?
|
||||||
|
''', (name, user_id))
|
||||||
|
|
||||||
|
if cursor.fetchone()[0] > 0:
|
||||||
|
raise ValueError(f"卡券名称已存在:{name}")
|
||||||
|
|
||||||
# 处理api_config参数 - 如果是字典则转换为JSON字符串
|
# 处理api_config参数 - 如果是字典则转换为JSON字符串
|
||||||
api_config_str = None
|
api_config_str = None
|
||||||
if api_config is not None:
|
if api_config is not None:
|
||||||
@ -1753,15 +1802,20 @@ class DBManager:
|
|||||||
else:
|
else:
|
||||||
api_config_str = str(api_config)
|
api_config_str = str(api_config)
|
||||||
|
|
||||||
cursor = self.conn.cursor()
|
|
||||||
cursor.execute('''
|
cursor.execute('''
|
||||||
INSERT INTO cards (name, type, api_config, text_content, data_content,
|
INSERT INTO cards (name, type, api_config, text_content, data_content,
|
||||||
description, enabled, delay_seconds, user_id)
|
description, enabled, delay_seconds, is_multi_spec,
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
spec_name, spec_value, user_id)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
''', (name, card_type, api_config_str, text_content, data_content,
|
''', (name, card_type, api_config_str, text_content, data_content,
|
||||||
description, enabled, delay_seconds, user_id))
|
description, enabled, delay_seconds, is_multi_spec,
|
||||||
|
spec_name, spec_value, user_id))
|
||||||
self.conn.commit()
|
self.conn.commit()
|
||||||
card_id = cursor.lastrowid
|
card_id = cursor.lastrowid
|
||||||
|
|
||||||
|
if is_multi_spec:
|
||||||
|
logger.info(f"创建多规格卡券成功: {name} - {spec_name}:{spec_value} (ID: {card_id})")
|
||||||
|
else:
|
||||||
logger.info(f"创建卡券成功: {name} (ID: {card_id})")
|
logger.info(f"创建卡券成功: {name} (ID: {card_id})")
|
||||||
return card_id
|
return card_id
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@ -1776,7 +1830,8 @@ class DBManager:
|
|||||||
if user_id is not None:
|
if user_id is not None:
|
||||||
cursor.execute('''
|
cursor.execute('''
|
||||||
SELECT id, name, type, api_config, text_content, data_content,
|
SELECT id, name, type, api_config, text_content, data_content,
|
||||||
description, enabled, delay_seconds, created_at, updated_at
|
description, enabled, delay_seconds, is_multi_spec,
|
||||||
|
spec_name, spec_value, created_at, updated_at
|
||||||
FROM cards
|
FROM cards
|
||||||
WHERE user_id = ?
|
WHERE user_id = ?
|
||||||
ORDER BY created_at DESC
|
ORDER BY created_at DESC
|
||||||
@ -1784,7 +1839,8 @@ class DBManager:
|
|||||||
else:
|
else:
|
||||||
cursor.execute('''
|
cursor.execute('''
|
||||||
SELECT id, name, type, api_config, text_content, data_content,
|
SELECT id, name, type, api_config, text_content, data_content,
|
||||||
description, enabled, delay_seconds, created_at, updated_at
|
description, enabled, delay_seconds, is_multi_spec,
|
||||||
|
spec_name, spec_value, created_at, updated_at
|
||||||
FROM cards
|
FROM cards
|
||||||
ORDER BY created_at DESC
|
ORDER BY created_at DESC
|
||||||
''')
|
''')
|
||||||
@ -1811,8 +1867,11 @@ class DBManager:
|
|||||||
'description': row[6],
|
'description': row[6],
|
||||||
'enabled': bool(row[7]),
|
'enabled': bool(row[7]),
|
||||||
'delay_seconds': row[8] or 0,
|
'delay_seconds': row[8] or 0,
|
||||||
'created_at': row[9],
|
'is_multi_spec': bool(row[9]) if row[9] is not None else False,
|
||||||
'updated_at': row[10]
|
'spec_name': row[10],
|
||||||
|
'spec_value': row[11],
|
||||||
|
'created_at': row[12],
|
||||||
|
'updated_at': row[13]
|
||||||
})
|
})
|
||||||
|
|
||||||
return cards
|
return cards
|
||||||
@ -1828,13 +1887,15 @@ class DBManager:
|
|||||||
if user_id is not None:
|
if user_id is not None:
|
||||||
cursor.execute('''
|
cursor.execute('''
|
||||||
SELECT id, name, type, api_config, text_content, data_content,
|
SELECT id, name, type, api_config, text_content, data_content,
|
||||||
description, enabled, delay_seconds, created_at, updated_at
|
description, enabled, delay_seconds, is_multi_spec,
|
||||||
|
spec_name, spec_value, created_at, updated_at
|
||||||
FROM cards WHERE id = ? AND user_id = ?
|
FROM cards WHERE id = ? AND user_id = ?
|
||||||
''', (card_id, user_id))
|
''', (card_id, user_id))
|
||||||
else:
|
else:
|
||||||
cursor.execute('''
|
cursor.execute('''
|
||||||
SELECT id, name, type, api_config, text_content, data_content,
|
SELECT id, name, type, api_config, text_content, data_content,
|
||||||
description, enabled, delay_seconds, created_at, updated_at
|
description, enabled, delay_seconds, is_multi_spec,
|
||||||
|
spec_name, spec_value, created_at, updated_at
|
||||||
FROM cards WHERE id = ?
|
FROM cards WHERE id = ?
|
||||||
''', (card_id,))
|
''', (card_id,))
|
||||||
|
|
||||||
@ -1860,8 +1921,11 @@ class DBManager:
|
|||||||
'description': row[6],
|
'description': row[6],
|
||||||
'enabled': bool(row[7]),
|
'enabled': bool(row[7]),
|
||||||
'delay_seconds': row[8] or 0,
|
'delay_seconds': row[8] or 0,
|
||||||
'created_at': row[9],
|
'is_multi_spec': bool(row[9]) if row[9] is not None else False,
|
||||||
'updated_at': row[10]
|
'spec_name': row[10],
|
||||||
|
'spec_value': row[11],
|
||||||
|
'created_at': row[12],
|
||||||
|
'updated_at': row[13]
|
||||||
}
|
}
|
||||||
return None
|
return None
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@ -1870,7 +1934,8 @@ class DBManager:
|
|||||||
|
|
||||||
def update_card(self, card_id: int, name: str = None, card_type: str = None,
|
def update_card(self, card_id: int, name: str = None, card_type: str = None,
|
||||||
api_config=None, text_content: str = None, data_content: str = None,
|
api_config=None, text_content: str = None, data_content: str = None,
|
||||||
description: str = None, enabled: bool = None, delay_seconds: int = None):
|
description: str = None, enabled: bool = None, delay_seconds: int = None,
|
||||||
|
is_multi_spec: bool = None, spec_name: str = None, spec_value: str = None):
|
||||||
"""更新卡券"""
|
"""更新卡券"""
|
||||||
with self.lock:
|
with self.lock:
|
||||||
try:
|
try:
|
||||||
@ -1913,6 +1978,15 @@ class DBManager:
|
|||||||
if delay_seconds is not None:
|
if delay_seconds is not None:
|
||||||
update_fields.append("delay_seconds = ?")
|
update_fields.append("delay_seconds = ?")
|
||||||
params.append(delay_seconds)
|
params.append(delay_seconds)
|
||||||
|
if is_multi_spec is not None:
|
||||||
|
update_fields.append("is_multi_spec = ?")
|
||||||
|
params.append(is_multi_spec)
|
||||||
|
if spec_name is not None:
|
||||||
|
update_fields.append("spec_name = ?")
|
||||||
|
params.append(spec_name)
|
||||||
|
if spec_value is not None:
|
||||||
|
update_fields.append("spec_value = ?")
|
||||||
|
params.append(spec_value)
|
||||||
|
|
||||||
if not update_fields:
|
if not update_fields:
|
||||||
return True # 没有需要更新的字段
|
return True # 没有需要更新的字段
|
||||||
@ -1964,7 +2038,8 @@ class DBManager:
|
|||||||
cursor.execute('''
|
cursor.execute('''
|
||||||
SELECT dr.id, dr.keyword, dr.card_id, dr.delivery_count, dr.enabled,
|
SELECT dr.id, dr.keyword, dr.card_id, dr.delivery_count, dr.enabled,
|
||||||
dr.description, dr.delivery_times, dr.created_at, dr.updated_at,
|
dr.description, dr.delivery_times, dr.created_at, dr.updated_at,
|
||||||
c.name as card_name, c.type as card_type
|
c.name as card_name, c.type as card_type,
|
||||||
|
c.is_multi_spec, c.spec_name, c.spec_value
|
||||||
FROM delivery_rules dr
|
FROM delivery_rules dr
|
||||||
LEFT JOIN cards c ON dr.card_id = c.id
|
LEFT JOIN cards c ON dr.card_id = c.id
|
||||||
WHERE dr.user_id = ?
|
WHERE dr.user_id = ?
|
||||||
@ -1974,7 +2049,8 @@ class DBManager:
|
|||||||
cursor.execute('''
|
cursor.execute('''
|
||||||
SELECT dr.id, dr.keyword, dr.card_id, dr.delivery_count, dr.enabled,
|
SELECT dr.id, dr.keyword, dr.card_id, dr.delivery_count, dr.enabled,
|
||||||
dr.description, dr.delivery_times, dr.created_at, dr.updated_at,
|
dr.description, dr.delivery_times, dr.created_at, dr.updated_at,
|
||||||
c.name as card_name, c.type as card_type
|
c.name as card_name, c.type as card_type,
|
||||||
|
c.is_multi_spec, c.spec_name, c.spec_value
|
||||||
FROM delivery_rules dr
|
FROM delivery_rules dr
|
||||||
LEFT JOIN cards c ON dr.card_id = c.id
|
LEFT JOIN cards c ON dr.card_id = c.id
|
||||||
ORDER BY dr.created_at DESC
|
ORDER BY dr.created_at DESC
|
||||||
@ -1993,7 +2069,10 @@ class DBManager:
|
|||||||
'created_at': row[7],
|
'created_at': row[7],
|
||||||
'updated_at': row[8],
|
'updated_at': row[8],
|
||||||
'card_name': row[9],
|
'card_name': row[9],
|
||||||
'card_type': row[10]
|
'card_type': row[10],
|
||||||
|
'is_multi_spec': bool(row[11]) if row[11] is not None else False,
|
||||||
|
'spec_name': row[12],
|
||||||
|
'spec_value': row[13]
|
||||||
})
|
})
|
||||||
|
|
||||||
return rules
|
return rules
|
||||||
@ -2047,9 +2126,9 @@ class DBManager:
|
|||||||
'delivery_times': row[6],
|
'delivery_times': row[6],
|
||||||
'card_name': row[7],
|
'card_name': row[7],
|
||||||
'card_type': row[8],
|
'card_type': row[8],
|
||||||
'card_api_config': api_config,
|
'api_config': api_config, # 修复字段名
|
||||||
'card_text_content': row[10],
|
'text_content': row[10],
|
||||||
'card_data_content': row[11],
|
'data_content': row[11],
|
||||||
'card_enabled': bool(row[12]),
|
'card_enabled': bool(row[12]),
|
||||||
'card_description': row[13], # 卡券备注信息
|
'card_description': row[13], # 卡券备注信息
|
||||||
'card_delay_seconds': row[14] or 0 # 延时秒数
|
'card_delay_seconds': row[14] or 0 # 延时秒数
|
||||||
@ -2173,6 +2252,136 @@ class DBManager:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"更新发货次数失败: {e}")
|
logger.error(f"更新发货次数失败: {e}")
|
||||||
|
|
||||||
|
def get_delivery_rules_by_keyword_and_spec(self, keyword: str, spec_name: str = None, spec_value: str = None):
|
||||||
|
"""根据关键字和规格信息获取匹配的发货规则(支持多规格)"""
|
||||||
|
with self.lock:
|
||||||
|
try:
|
||||||
|
cursor = self.conn.cursor()
|
||||||
|
|
||||||
|
# 优先匹配:卡券名称+规格名称+规格值
|
||||||
|
if spec_name and spec_value:
|
||||||
|
cursor.execute('''
|
||||||
|
SELECT dr.id, dr.keyword, dr.card_id, dr.delivery_count, dr.enabled,
|
||||||
|
dr.description, dr.delivery_times,
|
||||||
|
c.name as card_name, c.type as card_type, c.api_config,
|
||||||
|
c.text_content, c.data_content, c.enabled as card_enabled,
|
||||||
|
c.description as card_description, c.delay_seconds as card_delay_seconds,
|
||||||
|
c.is_multi_spec, c.spec_name, c.spec_value
|
||||||
|
FROM delivery_rules dr
|
||||||
|
LEFT JOIN cards c ON dr.card_id = c.id
|
||||||
|
WHERE dr.enabled = 1 AND c.enabled = 1
|
||||||
|
AND (? LIKE '%' || dr.keyword || '%' OR dr.keyword LIKE '%' || ? || '%')
|
||||||
|
AND c.is_multi_spec = 1 AND c.spec_name = ? AND c.spec_value = ?
|
||||||
|
ORDER BY
|
||||||
|
CASE
|
||||||
|
WHEN ? LIKE '%' || dr.keyword || '%' THEN LENGTH(dr.keyword)
|
||||||
|
ELSE LENGTH(dr.keyword) / 2
|
||||||
|
END DESC,
|
||||||
|
dr.delivery_times ASC
|
||||||
|
''', (keyword, keyword, spec_name, spec_value, keyword))
|
||||||
|
|
||||||
|
rules = []
|
||||||
|
for row in cursor.fetchall():
|
||||||
|
# 解析api_config JSON字符串
|
||||||
|
api_config = row[9]
|
||||||
|
if api_config:
|
||||||
|
try:
|
||||||
|
import json
|
||||||
|
api_config = json.loads(api_config)
|
||||||
|
except (json.JSONDecodeError, TypeError):
|
||||||
|
# 如果解析失败,保持原始字符串
|
||||||
|
pass
|
||||||
|
|
||||||
|
rules.append({
|
||||||
|
'id': row[0],
|
||||||
|
'keyword': row[1],
|
||||||
|
'card_id': row[2],
|
||||||
|
'delivery_count': row[3],
|
||||||
|
'enabled': bool(row[4]),
|
||||||
|
'description': row[5],
|
||||||
|
'delivery_times': row[6] or 0,
|
||||||
|
'card_name': row[7],
|
||||||
|
'card_type': row[8],
|
||||||
|
'api_config': api_config,
|
||||||
|
'text_content': row[10],
|
||||||
|
'data_content': row[11],
|
||||||
|
'card_enabled': bool(row[12]),
|
||||||
|
'card_description': row[13],
|
||||||
|
'card_delay_seconds': row[14] or 0,
|
||||||
|
'is_multi_spec': bool(row[15]),
|
||||||
|
'spec_name': row[16],
|
||||||
|
'spec_value': row[17]
|
||||||
|
})
|
||||||
|
|
||||||
|
if rules:
|
||||||
|
logger.info(f"找到多规格匹配规则: {keyword} - {spec_name}:{spec_value}")
|
||||||
|
return rules
|
||||||
|
|
||||||
|
# 兜底匹配:仅卡券名称
|
||||||
|
cursor.execute('''
|
||||||
|
SELECT dr.id, dr.keyword, dr.card_id, dr.delivery_count, dr.enabled,
|
||||||
|
dr.description, dr.delivery_times,
|
||||||
|
c.name as card_name, c.type as card_type, c.api_config,
|
||||||
|
c.text_content, c.data_content, c.enabled as card_enabled,
|
||||||
|
c.description as card_description, c.delay_seconds as card_delay_seconds,
|
||||||
|
c.is_multi_spec, c.spec_name, c.spec_value
|
||||||
|
FROM delivery_rules dr
|
||||||
|
LEFT JOIN cards c ON dr.card_id = c.id
|
||||||
|
WHERE dr.enabled = 1 AND c.enabled = 1
|
||||||
|
AND (? LIKE '%' || dr.keyword || '%' OR dr.keyword LIKE '%' || ? || '%')
|
||||||
|
AND (c.is_multi_spec = 0 OR c.is_multi_spec IS NULL)
|
||||||
|
ORDER BY
|
||||||
|
CASE
|
||||||
|
WHEN ? LIKE '%' || dr.keyword || '%' THEN LENGTH(dr.keyword)
|
||||||
|
ELSE LENGTH(dr.keyword) / 2
|
||||||
|
END DESC,
|
||||||
|
dr.delivery_times ASC
|
||||||
|
''', (keyword, keyword, keyword))
|
||||||
|
|
||||||
|
rules = []
|
||||||
|
for row in cursor.fetchall():
|
||||||
|
# 解析api_config JSON字符串
|
||||||
|
api_config = row[9]
|
||||||
|
if api_config:
|
||||||
|
try:
|
||||||
|
import json
|
||||||
|
api_config = json.loads(api_config)
|
||||||
|
except (json.JSONDecodeError, TypeError):
|
||||||
|
# 如果解析失败,保持原始字符串
|
||||||
|
pass
|
||||||
|
|
||||||
|
rules.append({
|
||||||
|
'id': row[0],
|
||||||
|
'keyword': row[1],
|
||||||
|
'card_id': row[2],
|
||||||
|
'delivery_count': row[3],
|
||||||
|
'enabled': bool(row[4]),
|
||||||
|
'description': row[5],
|
||||||
|
'delivery_times': row[6] or 0,
|
||||||
|
'card_name': row[7],
|
||||||
|
'card_type': row[8],
|
||||||
|
'api_config': api_config,
|
||||||
|
'text_content': row[10],
|
||||||
|
'data_content': row[11],
|
||||||
|
'card_enabled': bool(row[12]),
|
||||||
|
'card_description': row[13],
|
||||||
|
'card_delay_seconds': row[14] or 0,
|
||||||
|
'is_multi_spec': bool(row[15]) if row[15] is not None else False,
|
||||||
|
'spec_name': row[16],
|
||||||
|
'spec_value': row[17]
|
||||||
|
})
|
||||||
|
|
||||||
|
if rules:
|
||||||
|
logger.info(f"找到兜底匹配规则: {keyword}")
|
||||||
|
else:
|
||||||
|
logger.info(f"未找到匹配规则: {keyword}")
|
||||||
|
|
||||||
|
return rules
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"获取发货规则失败: {e}")
|
||||||
|
return []
|
||||||
|
|
||||||
def delete_card(self, card_id: int):
|
def delete_card(self, card_id: int):
|
||||||
"""删除卡券"""
|
"""删除卡券"""
|
||||||
with self.lock:
|
with self.lock:
|
||||||
@ -2463,6 +2672,49 @@ class DBManager:
|
|||||||
logger.error(f"获取商品信息失败: {e}")
|
logger.error(f"获取商品信息失败: {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def update_item_multi_spec_status(self, cookie_id: str, item_id: str, is_multi_spec: bool) -> bool:
|
||||||
|
"""更新商品的多规格状态"""
|
||||||
|
try:
|
||||||
|
with self.lock:
|
||||||
|
cursor = self.conn.cursor()
|
||||||
|
cursor.execute('''
|
||||||
|
UPDATE item_info
|
||||||
|
SET is_multi_spec = ?, updated_at = CURRENT_TIMESTAMP
|
||||||
|
WHERE cookie_id = ? AND item_id = ?
|
||||||
|
''', (is_multi_spec, cookie_id, item_id))
|
||||||
|
|
||||||
|
if cursor.rowcount > 0:
|
||||||
|
self.conn.commit()
|
||||||
|
logger.info(f"更新商品多规格状态成功: {item_id} -> {is_multi_spec}")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
logger.warning(f"商品不存在,无法更新多规格状态: {item_id}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"更新商品多规格状态失败: {e}")
|
||||||
|
self.conn.rollback()
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_item_multi_spec_status(self, cookie_id: str, item_id: str) -> bool:
|
||||||
|
"""获取商品的多规格状态"""
|
||||||
|
try:
|
||||||
|
with self.lock:
|
||||||
|
cursor = self.conn.cursor()
|
||||||
|
cursor.execute('''
|
||||||
|
SELECT is_multi_spec FROM item_info
|
||||||
|
WHERE cookie_id = ? AND item_id = ?
|
||||||
|
''', (cookie_id, item_id))
|
||||||
|
|
||||||
|
row = cursor.fetchone()
|
||||||
|
if row:
|
||||||
|
return bool(row[0]) if row[0] is not None else False
|
||||||
|
return False
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"获取商品多规格状态失败: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
def get_items_by_cookie(self, cookie_id: str) -> List[Dict]:
|
def get_items_by_cookie(self, cookie_id: str) -> List[Dict]:
|
||||||
"""获取指定Cookie的所有商品信息
|
"""获取指定Cookie的所有商品信息
|
||||||
|
|
||||||
|
@ -1737,6 +1737,12 @@ def create_card(card_data: dict, current_user: Dict[str, Any] = Depends(get_curr
|
|||||||
|
|
||||||
log_with_user('info', f"创建卡券: {card_name}", current_user)
|
log_with_user('info', f"创建卡券: {card_name}", current_user)
|
||||||
|
|
||||||
|
# 验证多规格字段
|
||||||
|
is_multi_spec = card_data.get('is_multi_spec', False)
|
||||||
|
if is_multi_spec:
|
||||||
|
if not card_data.get('spec_name') or not card_data.get('spec_value'):
|
||||||
|
raise HTTPException(status_code=400, detail="多规格卡券必须提供规格名称和规格值")
|
||||||
|
|
||||||
card_id = db_manager.create_card(
|
card_id = db_manager.create_card(
|
||||||
name=card_data.get('name'),
|
name=card_data.get('name'),
|
||||||
card_type=card_data.get('type'),
|
card_type=card_data.get('type'),
|
||||||
@ -1746,6 +1752,9 @@ def create_card(card_data: dict, current_user: Dict[str, Any] = Depends(get_curr
|
|||||||
description=card_data.get('description'),
|
description=card_data.get('description'),
|
||||||
enabled=card_data.get('enabled', True),
|
enabled=card_data.get('enabled', True),
|
||||||
delay_seconds=card_data.get('delay_seconds', 0),
|
delay_seconds=card_data.get('delay_seconds', 0),
|
||||||
|
is_multi_spec=is_multi_spec,
|
||||||
|
spec_name=card_data.get('spec_name') if is_multi_spec else None,
|
||||||
|
spec_value=card_data.get('spec_value') if is_multi_spec else None,
|
||||||
user_id=user_id
|
user_id=user_id
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1776,6 +1785,12 @@ def update_card(card_id: int, card_data: dict, _: None = Depends(require_auth)):
|
|||||||
"""更新卡券"""
|
"""更新卡券"""
|
||||||
try:
|
try:
|
||||||
from db_manager import db_manager
|
from db_manager import db_manager
|
||||||
|
# 验证多规格字段
|
||||||
|
is_multi_spec = card_data.get('is_multi_spec')
|
||||||
|
if is_multi_spec:
|
||||||
|
if not card_data.get('spec_name') or not card_data.get('spec_value'):
|
||||||
|
raise HTTPException(status_code=400, detail="多规格卡券必须提供规格名称和规格值")
|
||||||
|
|
||||||
success = db_manager.update_card(
|
success = db_manager.update_card(
|
||||||
card_id=card_id,
|
card_id=card_id,
|
||||||
name=card_data.get('name'),
|
name=card_data.get('name'),
|
||||||
@ -1785,7 +1800,10 @@ def update_card(card_id: int, card_data: dict, _: None = Depends(require_auth)):
|
|||||||
data_content=card_data.get('data_content'),
|
data_content=card_data.get('data_content'),
|
||||||
description=card_data.get('description'),
|
description=card_data.get('description'),
|
||||||
enabled=card_data.get('enabled', True),
|
enabled=card_data.get('enabled', True),
|
||||||
delay_seconds=card_data.get('delay_seconds')
|
delay_seconds=card_data.get('delay_seconds'),
|
||||||
|
is_multi_spec=is_multi_spec,
|
||||||
|
spec_name=card_data.get('spec_name'),
|
||||||
|
spec_value=card_data.get('spec_value')
|
||||||
)
|
)
|
||||||
if success:
|
if success:
|
||||||
return {"message": "卡券更新成功"}
|
return {"message": "卡券更新成功"}
|
||||||
@ -3039,5 +3057,25 @@ def clear_table_data(table_name: str, admin_user: Dict[str, Any] = Depends(requi
|
|||||||
raise HTTPException(status_code=500, detail=str(e))
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
||||||
|
|
||||||
|
# 商品多规格管理API
|
||||||
|
@app.put("/items/{cookie_id}/{item_id}/multi-spec")
|
||||||
|
def update_item_multi_spec(cookie_id: str, item_id: str, spec_data: dict, _: None = Depends(require_auth)):
|
||||||
|
"""更新商品的多规格状态"""
|
||||||
|
try:
|
||||||
|
from db_manager import db_manager
|
||||||
|
|
||||||
|
is_multi_spec = spec_data.get('is_multi_spec', False)
|
||||||
|
|
||||||
|
success = db_manager.update_item_multi_spec_status(cookie_id, item_id, is_multi_spec)
|
||||||
|
|
||||||
|
if success:
|
||||||
|
return {"message": f"商品多规格状态已{'开启' if is_multi_spec else '关闭'}"}
|
||||||
|
else:
|
||||||
|
raise HTTPException(status_code=404, detail="商品不存在")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
uvicorn.run(app, host="0.0.0.0", port=8080)
|
uvicorn.run(app, host="0.0.0.0", port=8080)
|
@ -2,7 +2,7 @@
|
|||||||
# 闲鱼自动回复系统 - Python依赖包
|
# 闲鱼自动回复系统 - Python依赖包
|
||||||
# ================================
|
# ================================
|
||||||
|
|
||||||
# Web框架和API相关
|
# 核心Web框架
|
||||||
fastapi>=0.111.0
|
fastapi>=0.111.0
|
||||||
uvicorn[standard]>=0.29.0
|
uvicorn[standard]>=0.29.0
|
||||||
pydantic>=2.7.0
|
pydantic>=2.7.0
|
||||||
@ -11,11 +11,13 @@ pydantic>=2.7.0
|
|||||||
loguru>=0.7.0
|
loguru>=0.7.0
|
||||||
|
|
||||||
# 网络通信
|
# 网络通信
|
||||||
websockets>=10.0,<13.0
|
websockets>=12.0
|
||||||
aiohttp>=3.9.0
|
aiohttp>=3.9.0
|
||||||
|
requests>=2.31.0
|
||||||
|
|
||||||
# 配置文件处理
|
# 配置文件处理
|
||||||
PyYAML>=6.0.0
|
PyYAML>=6.0.0
|
||||||
|
python-dotenv>=1.0.1
|
||||||
|
|
||||||
# JavaScript执行引擎
|
# JavaScript执行引擎
|
||||||
PyExecJS>=1.5.1
|
PyExecJS>=1.5.1
|
||||||
@ -26,26 +28,21 @@ blackboxprotobuf>=1.0.1
|
|||||||
# 系统监控
|
# 系统监控
|
||||||
psutil>=5.9.0
|
psutil>=5.9.0
|
||||||
|
|
||||||
# HTTP客户端
|
|
||||||
requests>=2.31.0
|
|
||||||
|
|
||||||
# 文件上传支持
|
# 文件上传支持
|
||||||
python-multipart>=0.0.6
|
python-multipart>=0.0.6
|
||||||
|
|
||||||
# AI回复相关
|
# AI回复引擎
|
||||||
openai>=1.65.5
|
openai>=1.65.5
|
||||||
python-dotenv>=1.0.1
|
|
||||||
|
|
||||||
# 图像处理(图形验证码)
|
# 图像处理(验证码生成)
|
||||||
Pillow>=10.0.0
|
Pillow>=10.0.0
|
||||||
|
|
||||||
# 浏览器自动化(商品搜索功能)
|
# 浏览器自动化(商品搜索、订单详情获取)
|
||||||
playwright>=1.40.0
|
playwright>=1.40.0
|
||||||
|
|
||||||
# 加密和安全
|
# 加密和安全
|
||||||
PyJWT>=2.8.0
|
PyJWT>=2.8.0
|
||||||
passlib>=1.7.4
|
passlib[bcrypt]>=1.7.4
|
||||||
bcrypt>=4.0.1
|
|
||||||
cryptography>=41.0.0
|
cryptography>=41.0.0
|
||||||
|
|
||||||
# 时间处理
|
# 时间处理
|
||||||
@ -54,7 +51,7 @@ python-dateutil>=2.8.2
|
|||||||
# 正则表达式增强
|
# 正则表达式增强
|
||||||
regex>=2023.10.3
|
regex>=2023.10.3
|
||||||
|
|
||||||
# Excel文件处理(导入导出功能)
|
# Excel文件处理(数据导入导出)
|
||||||
pandas>=2.0.0
|
pandas>=2.0.0
|
||||||
openpyxl>=3.1.0
|
openpyxl>=3.1.0
|
||||||
|
|
||||||
|
@ -1419,12 +1419,13 @@
|
|||||||
<th style="width: 5%">
|
<th style="width: 5%">
|
||||||
<input type="checkbox" id="selectAllItems" onchange="toggleSelectAll(this)">
|
<input type="checkbox" id="selectAllItems" onchange="toggleSelectAll(this)">
|
||||||
</th>
|
</th>
|
||||||
<th style="width: 15%">账号ID</th>
|
<th style="width: 12%">账号ID</th>
|
||||||
<th style="width: 15%">商品ID</th>
|
<th style="width: 12%">商品ID</th>
|
||||||
<th style="width: 20%">商品标题</th>
|
<th style="width: 18%">商品标题</th>
|
||||||
<th style="width: 25%">商品详情</th>
|
<th style="width: 20%">商品详情</th>
|
||||||
<th style="width: 12%">更新时间</th>
|
<th style="width: 8%">多规格</th>
|
||||||
<th style="width: 8%">操作</th>
|
<th style="width: 10%">更新时间</th>
|
||||||
|
<th style="width: 15%">操作</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="itemsTableBody">
|
<tbody id="itemsTableBody">
|
||||||
@ -1567,6 +1568,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th>卡券名称</th>
|
<th>卡券名称</th>
|
||||||
<th>类型</th>
|
<th>类型</th>
|
||||||
|
<th>规格信息</th>
|
||||||
<th>数据量</th>
|
<th>数据量</th>
|
||||||
<th>延时时间</th>
|
<th>延时时间</th>
|
||||||
<th>状态</th>
|
<th>状态</th>
|
||||||
@ -2157,7 +2159,7 @@
|
|||||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<form id="addCardForm">
|
<form id="addCardForm" onsubmit="event.preventDefault(); saveCard(); return false;">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">卡券名称 <span class="text-danger">*</span></label>
|
<label class="form-label">卡券名称 <span class="text-danger">*</span></label>
|
||||||
<input type="text" class="form-control" id="cardName" placeholder="例如:游戏点卡、会员卡等" required>
|
<input type="text" class="form-control" id="cardName" placeholder="例如:游戏点卡、会员卡等" required>
|
||||||
@ -2253,6 +2255,46 @@
|
|||||||
备注内容会与发货内容一起发送。使用 <code>{DELIVERY_CONTENT}</code> 变量可以在备注中插入实际的发货内容。
|
备注内容会与发货内容一起发送。使用 <code>{DELIVERY_CONTENT}</code> 变量可以在备注中插入实际的发货内容。
|
||||||
</small>
|
</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 多规格设置 -->
|
||||||
|
<div class="mb-3">
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<input class="form-check-input" type="checkbox" id="isMultiSpec" onchange="toggleMultiSpecFields()">
|
||||||
|
<label class="form-check-label" for="isMultiSpec">
|
||||||
|
<strong>多规格卡券</strong>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-text">开启后可以为同一商品的不同规格创建不同的卡券</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 多规格字段 -->
|
||||||
|
<div id="multiSpecFields" style="display: none;">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">规格名称 <span class="text-danger">*</span></label>
|
||||||
|
<input type="text" class="form-control" id="specName" placeholder="例如:套餐类型、颜色、尺寸">
|
||||||
|
<div class="form-text">规格的名称,如套餐类型、颜色等</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">规格值 <span class="text-danger">*</span></label>
|
||||||
|
<input type="text" class="form-control" id="specValue" placeholder="例如:30天、红色、XL">
|
||||||
|
<div class="form-text">具体的规格值,如30天、红色等</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="alert alert-info">
|
||||||
|
<i class="bi bi-info-circle"></i>
|
||||||
|
<strong>多规格说明:</strong>
|
||||||
|
<ul class="mb-0 mt-2">
|
||||||
|
<li>同一卡券名称可以创建多个不同规格的卡券</li>
|
||||||
|
<li>卡券名称+规格名称+规格值必须唯一</li>
|
||||||
|
<li>自动发货时会优先匹配精确规格,找不到时使用普通卡券兜底</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
@ -2380,6 +2422,46 @@
|
|||||||
备注内容会与发货内容一起发送。使用 <code>{DELIVERY_CONTENT}</code> 变量可以在备注中插入实际的发货内容。
|
备注内容会与发货内容一起发送。使用 <code>{DELIVERY_CONTENT}</code> 变量可以在备注中插入实际的发货内容。
|
||||||
</small>
|
</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 多规格设置 -->
|
||||||
|
<div class="mb-3">
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<input class="form-check-input" type="checkbox" id="editIsMultiSpec" onchange="toggleEditMultiSpecFields()">
|
||||||
|
<label class="form-check-label" for="editIsMultiSpec">
|
||||||
|
<strong>多规格卡券</strong>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-text">开启后可以为同一商品的不同规格创建不同的卡券</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 多规格字段 -->
|
||||||
|
<div id="editMultiSpecFields" style="display: none;">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">规格名称 <span class="text-danger">*</span></label>
|
||||||
|
<input type="text" class="form-control" id="editSpecName" placeholder="例如:套餐类型、颜色、尺寸">
|
||||||
|
<div class="form-text">规格的名称,如套餐类型、颜色等</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">规格值 <span class="text-danger">*</span></label>
|
||||||
|
<input type="text" class="form-control" id="editSpecValue" placeholder="例如:30天、红色、XL">
|
||||||
|
<div class="form-text">具体的规格值,如30天、红色等</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="alert alert-info">
|
||||||
|
<i class="bi bi-info-circle"></i>
|
||||||
|
<strong>多规格说明:</strong>
|
||||||
|
<ul class="mb-0 mt-2">
|
||||||
|
<li>同一卡券名称可以创建多个不同规格的卡券</li>
|
||||||
|
<li>卡券名称+规格名称+规格值必须唯一</li>
|
||||||
|
<li>自动发货时会优先匹配精确规格,找不到时使用普通卡券兜底</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
@ -5021,7 +5103,7 @@
|
|||||||
if (cards.length === 0) {
|
if (cards.length === 0) {
|
||||||
tbody.innerHTML = `
|
tbody.innerHTML = `
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="7" class="text-center py-4 text-muted">
|
<td colspan="8" class="text-center py-4 text-muted">
|
||||||
<i class="bi bi-credit-card fs-1 d-block mb-3"></i>
|
<i class="bi bi-credit-card fs-1 d-block mb-3"></i>
|
||||||
<h5>暂无卡券数据</h5>
|
<h5>暂无卡券数据</h5>
|
||||||
<p class="mb-0">点击"添加卡券"开始创建您的第一个卡券</p>
|
<p class="mb-0">点击"添加卡券"开始创建您的第一个卡券</p>
|
||||||
@ -5071,12 +5153,19 @@
|
|||||||
`${card.delay_seconds}秒` :
|
`${card.delay_seconds}秒` :
|
||||||
'<span class="text-muted">立即</span>';
|
'<span class="text-muted">立即</span>';
|
||||||
|
|
||||||
|
// 规格信息显示
|
||||||
|
let specDisplay = '<span class="text-muted">普通卡券</span>';
|
||||||
|
if (card.is_multi_spec && card.spec_name && card.spec_value) {
|
||||||
|
specDisplay = `<span class="badge bg-primary">${card.spec_name}: ${card.spec_value}</span>`;
|
||||||
|
}
|
||||||
|
|
||||||
tr.innerHTML = `
|
tr.innerHTML = `
|
||||||
<td>
|
<td>
|
||||||
<div class="fw-bold">${card.name}</div>
|
<div class="fw-bold">${card.name}</div>
|
||||||
${card.description ? `<small class="text-muted">${card.description}</small>` : ''}
|
${card.description ? `<small class="text-muted">${card.description}</small>` : ''}
|
||||||
</td>
|
</td>
|
||||||
<td>${typeBadge}</td>
|
<td>${typeBadge}</td>
|
||||||
|
<td>${specDisplay}</td>
|
||||||
<td>${dataCount}</td>
|
<td>${dataCount}</td>
|
||||||
<td>${delayDisplay}</td>
|
<td>${delayDisplay}</td>
|
||||||
<td>${statusBadge}</td>
|
<td>${statusBadge}</td>
|
||||||
@ -5132,6 +5221,93 @@
|
|||||||
document.getElementById('dataFields').style.display = cardType === 'data' ? 'block' : 'none';
|
document.getElementById('dataFields').style.display = cardType === 'data' ? 'block' : 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 切换多规格字段显示
|
||||||
|
function toggleMultiSpecFields() {
|
||||||
|
const isMultiSpec = document.getElementById('isMultiSpec').checked;
|
||||||
|
document.getElementById('multiSpecFields').style.display = isMultiSpec ? 'block' : 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 切换编辑多规格字段显示
|
||||||
|
function toggleEditMultiSpecFields() {
|
||||||
|
const checkbox = document.getElementById('editIsMultiSpec');
|
||||||
|
const fieldsDiv = document.getElementById('editMultiSpecFields');
|
||||||
|
|
||||||
|
if (!checkbox) {
|
||||||
|
console.error('编辑多规格开关元素未找到');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fieldsDiv) {
|
||||||
|
console.error('编辑多规格字段容器未找到');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isMultiSpec = checkbox.checked;
|
||||||
|
const displayStyle = isMultiSpec ? 'block' : 'none';
|
||||||
|
|
||||||
|
console.log('toggleEditMultiSpecFields - 多规格状态:', isMultiSpec);
|
||||||
|
console.log('toggleEditMultiSpecFields - 设置显示样式:', displayStyle);
|
||||||
|
|
||||||
|
fieldsDiv.style.display = displayStyle;
|
||||||
|
|
||||||
|
// 验证设置是否生效
|
||||||
|
console.log('toggleEditMultiSpecFields - 实际显示样式:', fieldsDiv.style.display);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清空添加卡券表单
|
||||||
|
function clearAddCardForm() {
|
||||||
|
try {
|
||||||
|
// 安全地清空表单字段
|
||||||
|
const setElementValue = (id, value) => {
|
||||||
|
const element = document.getElementById(id);
|
||||||
|
if (element) {
|
||||||
|
if (element.type === 'checkbox') {
|
||||||
|
element.checked = value;
|
||||||
|
} else {
|
||||||
|
element.value = value;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.warn(`Element with id '${id}' not found`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const setElementDisplay = (id, display) => {
|
||||||
|
const element = document.getElementById(id);
|
||||||
|
if (element) {
|
||||||
|
element.style.display = display;
|
||||||
|
} else {
|
||||||
|
console.warn(`Element with id '${id}' not found`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 清空基本字段
|
||||||
|
setElementValue('cardName', '');
|
||||||
|
setElementValue('cardType', 'text');
|
||||||
|
setElementValue('cardDescription', '');
|
||||||
|
setElementValue('cardDelaySeconds', '0');
|
||||||
|
setElementValue('isMultiSpec', false);
|
||||||
|
setElementValue('specName', '');
|
||||||
|
setElementValue('specValue', '');
|
||||||
|
|
||||||
|
// 隐藏多规格字段
|
||||||
|
setElementDisplay('multiSpecFields', 'none');
|
||||||
|
|
||||||
|
// 清空类型相关字段
|
||||||
|
setElementValue('textContent', '');
|
||||||
|
setElementValue('dataContent', '');
|
||||||
|
setElementValue('apiUrl', '');
|
||||||
|
setElementValue('apiMethod', 'GET');
|
||||||
|
setElementValue('apiHeaders', '');
|
||||||
|
setElementValue('apiParams', '');
|
||||||
|
setElementValue('apiTimeout', '10');
|
||||||
|
|
||||||
|
// 重置字段显示
|
||||||
|
toggleCardTypeFields();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('清空表单时出错:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 保存卡券
|
// 保存卡券
|
||||||
async function saveCard() {
|
async function saveCard() {
|
||||||
try {
|
try {
|
||||||
@ -5143,12 +5319,26 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 检查多规格设置
|
||||||
|
const isMultiSpec = document.getElementById('isMultiSpec').checked;
|
||||||
|
const specName = document.getElementById('specName').value;
|
||||||
|
const specValue = document.getElementById('specValue').value;
|
||||||
|
|
||||||
|
// 验证多规格字段
|
||||||
|
if (isMultiSpec && (!specName || !specValue)) {
|
||||||
|
showToast('多规格卡券必须填写规格名称和规格值', 'warning');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const cardData = {
|
const cardData = {
|
||||||
name: cardName,
|
name: cardName,
|
||||||
type: cardType,
|
type: cardType,
|
||||||
description: document.getElementById('cardDescription').value,
|
description: document.getElementById('cardDescription').value,
|
||||||
delay_seconds: parseInt(document.getElementById('cardDelaySeconds').value) || 0,
|
delay_seconds: parseInt(document.getElementById('cardDelaySeconds').value) || 0,
|
||||||
enabled: true
|
enabled: true,
|
||||||
|
is_multi_spec: isMultiSpec,
|
||||||
|
spec_name: isMultiSpec ? specName : null,
|
||||||
|
spec_value: isMultiSpec ? specValue : null
|
||||||
};
|
};
|
||||||
|
|
||||||
// 根据类型添加特定配置
|
// 根据类型添加特定配置
|
||||||
@ -5208,14 +5398,28 @@
|
|||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
showToast('卡券保存成功', 'success');
|
showToast('卡券保存成功', 'success');
|
||||||
bootstrap.Modal.getInstance(document.getElementById('addCardModal')).hide();
|
bootstrap.Modal.getInstance(document.getElementById('addCardModal')).hide();
|
||||||
|
// 清空表单
|
||||||
|
clearAddCardForm();
|
||||||
loadCards();
|
loadCards();
|
||||||
} else {
|
} else {
|
||||||
const error = await response.text();
|
let errorMessage = '保存失败';
|
||||||
showToast(`保存失败: ${error}`, 'danger');
|
try {
|
||||||
|
const errorData = await response.json();
|
||||||
|
errorMessage = errorData.error || errorData.detail || errorMessage;
|
||||||
|
} catch (e) {
|
||||||
|
// 如果不是JSON格式,尝试获取文本
|
||||||
|
try {
|
||||||
|
const errorText = await response.text();
|
||||||
|
errorMessage = errorText || errorMessage;
|
||||||
|
} catch (e2) {
|
||||||
|
errorMessage = `HTTP ${response.status}: ${response.statusText}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
showToast(`保存失败: ${errorMessage}`, 'danger');
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('保存卡券失败:', error);
|
console.error('保存卡券失败:', error);
|
||||||
showToast('保存卡券失败', 'danger');
|
showToast(`网络错误: ${error.message}`, 'danger');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// ==================== 自动发货功能 ====================
|
// ==================== 自动发货功能 ====================
|
||||||
@ -5294,7 +5498,12 @@
|
|||||||
${rule.description ? `<small class="text-muted">${rule.description}</small>` : ''}
|
${rule.description ? `<small class="text-muted">${rule.description}</small>` : ''}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
<div>
|
||||||
<span class="badge bg-primary">${rule.card_name || '未知卡券'}</span>
|
<span class="badge bg-primary">${rule.card_name || '未知卡券'}</span>
|
||||||
|
${rule.is_multi_spec && rule.spec_name && rule.spec_value ?
|
||||||
|
`<br><small class="text-muted mt-1 d-block"><i class="bi bi-tags"></i> ${rule.spec_name}: ${rule.spec_value}</small>` :
|
||||||
|
''}
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>${cardTypeBadge}</td>
|
<td>${cardTypeBadge}</td>
|
||||||
<td>
|
<td>
|
||||||
@ -5364,7 +5573,20 @@
|
|||||||
if (card.enabled) { // 只显示启用的卡券
|
if (card.enabled) { // 只显示启用的卡券
|
||||||
const option = document.createElement('option');
|
const option = document.createElement('option');
|
||||||
option.value = card.id;
|
option.value = card.id;
|
||||||
option.textContent = `${card.name} (${card.type === 'api' ? 'API' : card.type === 'text' ? '固定文字' : '批量数据'})`;
|
|
||||||
|
// 构建显示文本
|
||||||
|
let displayText = card.name;
|
||||||
|
|
||||||
|
// 添加类型信息
|
||||||
|
const typeText = card.type === 'api' ? 'API' : card.type === 'text' ? '固定文字' : '批量数据';
|
||||||
|
displayText += ` (${typeText})`;
|
||||||
|
|
||||||
|
// 添加规格信息
|
||||||
|
if (card.is_multi_spec && card.spec_name && card.spec_value) {
|
||||||
|
displayText += ` [${card.spec_name}:${card.spec_value}]`;
|
||||||
|
}
|
||||||
|
|
||||||
|
option.textContent = displayText;
|
||||||
select.appendChild(option);
|
select.appendChild(option);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -5440,6 +5662,17 @@
|
|||||||
document.getElementById('editCardDelaySeconds').value = card.delay_seconds || 0;
|
document.getElementById('editCardDelaySeconds').value = card.delay_seconds || 0;
|
||||||
document.getElementById('editCardEnabled').checked = card.enabled;
|
document.getElementById('editCardEnabled').checked = card.enabled;
|
||||||
|
|
||||||
|
// 填充多规格字段
|
||||||
|
const isMultiSpec = card.is_multi_spec || false;
|
||||||
|
document.getElementById('editIsMultiSpec').checked = isMultiSpec;
|
||||||
|
document.getElementById('editSpecName').value = card.spec_name || '';
|
||||||
|
document.getElementById('editSpecValue').value = card.spec_value || '';
|
||||||
|
|
||||||
|
// 添加调试日志
|
||||||
|
console.log('编辑卡券 - 多规格状态:', isMultiSpec);
|
||||||
|
console.log('编辑卡券 - 规格名称:', card.spec_name);
|
||||||
|
console.log('编辑卡券 - 规格值:', card.spec_value);
|
||||||
|
|
||||||
// 根据类型填充特定字段
|
// 根据类型填充特定字段
|
||||||
if (card.type === 'api' && card.api_config) {
|
if (card.type === 'api' && card.api_config) {
|
||||||
document.getElementById('editApiUrl').value = card.api_config.url || '';
|
document.getElementById('editApiUrl').value = card.api_config.url || '';
|
||||||
@ -5456,6 +5689,19 @@
|
|||||||
// 显示对应的字段
|
// 显示对应的字段
|
||||||
toggleEditCardTypeFields();
|
toggleEditCardTypeFields();
|
||||||
|
|
||||||
|
// 使用延迟调用确保DOM更新完成后再显示多规格字段
|
||||||
|
setTimeout(() => {
|
||||||
|
console.log('延迟调用 toggleEditMultiSpecFields');
|
||||||
|
toggleEditMultiSpecFields();
|
||||||
|
|
||||||
|
// 验证多规格字段是否正确显示
|
||||||
|
const multiSpecElement = document.getElementById('editMultiSpecFields');
|
||||||
|
const isChecked = document.getElementById('editIsMultiSpec').checked;
|
||||||
|
console.log('多规格元素存在:', !!multiSpecElement);
|
||||||
|
console.log('多规格开关状态:', isChecked);
|
||||||
|
console.log('多规格字段显示状态:', multiSpecElement ? multiSpecElement.style.display : 'element not found');
|
||||||
|
}, 100);
|
||||||
|
|
||||||
// 显示模态框
|
// 显示模态框
|
||||||
const modal = new bootstrap.Modal(document.getElementById('editCardModal'));
|
const modal = new bootstrap.Modal(document.getElementById('editCardModal'));
|
||||||
modal.show();
|
modal.show();
|
||||||
@ -5489,12 +5735,26 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 检查多规格设置
|
||||||
|
const isMultiSpec = document.getElementById('editIsMultiSpec').checked;
|
||||||
|
const specName = document.getElementById('editSpecName').value;
|
||||||
|
const specValue = document.getElementById('editSpecValue').value;
|
||||||
|
|
||||||
|
// 验证多规格字段
|
||||||
|
if (isMultiSpec && (!specName || !specValue)) {
|
||||||
|
showToast('多规格卡券必须填写规格名称和规格值', 'warning');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const cardData = {
|
const cardData = {
|
||||||
name: cardName,
|
name: cardName,
|
||||||
type: cardType,
|
type: cardType,
|
||||||
description: document.getElementById('editCardDescription').value,
|
description: document.getElementById('editCardDescription').value,
|
||||||
delay_seconds: parseInt(document.getElementById('editCardDelaySeconds').value) || 0,
|
delay_seconds: parseInt(document.getElementById('editCardDelaySeconds').value) || 0,
|
||||||
enabled: document.getElementById('editCardEnabled').checked
|
enabled: document.getElementById('editCardEnabled').checked,
|
||||||
|
is_multi_spec: isMultiSpec,
|
||||||
|
spec_name: isMultiSpec ? specName : null,
|
||||||
|
spec_value: isMultiSpec ? specValue : null
|
||||||
};
|
};
|
||||||
|
|
||||||
// 根据类型添加特定配置
|
// 根据类型添加特定配置
|
||||||
@ -5651,7 +5911,20 @@
|
|||||||
if (card.enabled) { // 只显示启用的卡券
|
if (card.enabled) { // 只显示启用的卡券
|
||||||
const option = document.createElement('option');
|
const option = document.createElement('option');
|
||||||
option.value = card.id;
|
option.value = card.id;
|
||||||
option.textContent = `${card.name} (${card.type === 'api' ? 'API' : card.type === 'text' ? '固定文字' : '批量数据'})`;
|
|
||||||
|
// 构建显示文本
|
||||||
|
let displayText = card.name;
|
||||||
|
|
||||||
|
// 添加类型信息
|
||||||
|
const typeText = card.type === 'api' ? 'API' : card.type === 'text' ? '固定文字' : '批量数据';
|
||||||
|
displayText += ` (${typeText})`;
|
||||||
|
|
||||||
|
// 添加规格信息
|
||||||
|
if (card.is_multi_spec && card.spec_name && card.spec_value) {
|
||||||
|
displayText += ` [${card.spec_name}:${card.spec_value}]`;
|
||||||
|
}
|
||||||
|
|
||||||
|
option.textContent = displayText;
|
||||||
select.appendChild(option);
|
select.appendChild(option);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -6316,6 +6589,34 @@
|
|||||||
|
|
||||||
// ==================== 商品管理功能 ====================
|
// ==================== 商品管理功能 ====================
|
||||||
|
|
||||||
|
// 切换商品多规格状态
|
||||||
|
async function toggleItemMultiSpec(cookieId, itemId, isMultiSpec) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${apiBase}/items/${encodeURIComponent(cookieId)}/${encodeURIComponent(itemId)}/multi-spec`, {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': `Bearer ${authToken}`
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
is_multi_spec: isMultiSpec
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
showToast(`${isMultiSpec ? '开启' : '关闭'}多规格成功`, 'success');
|
||||||
|
// 刷新商品列表
|
||||||
|
await refreshItemsData();
|
||||||
|
} else {
|
||||||
|
const errorData = await response.json();
|
||||||
|
throw new Error(errorData.error || '操作失败');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('切换多规格状态失败:', error);
|
||||||
|
showToast(`切换多规格状态失败: ${error.message}`, 'danger');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 加载商品列表
|
// 加载商品列表
|
||||||
async function loadItems() {
|
async function loadItems() {
|
||||||
try {
|
try {
|
||||||
@ -6475,7 +6776,7 @@
|
|||||||
const tbody = document.getElementById('itemsTableBody');
|
const tbody = document.getElementById('itemsTableBody');
|
||||||
|
|
||||||
if (!items || items.length === 0) {
|
if (!items || items.length === 0) {
|
||||||
tbody.innerHTML = '<tr><td colspan="7" class="text-center text-muted">暂无商品数据</td></tr>';
|
tbody.innerHTML = '<tr><td colspan="8" class="text-center text-muted">暂无商品数据</td></tr>';
|
||||||
// 重置选择状态
|
// 重置选择状态
|
||||||
const selectAllCheckbox = document.getElementById('selectAllItems');
|
const selectAllCheckbox = document.getElementById('selectAllItems');
|
||||||
if (selectAllCheckbox) {
|
if (selectAllCheckbox) {
|
||||||
@ -6511,6 +6812,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 多规格状态显示
|
||||||
|
const isMultiSpec = item.is_multi_spec;
|
||||||
|
const multiSpecDisplay = isMultiSpec ?
|
||||||
|
'<span class="badge bg-success">多规格</span>' :
|
||||||
|
'<span class="badge bg-secondary">普通</span>';
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
@ -6523,6 +6830,7 @@
|
|||||||
<td>${escapeHtml(item.item_id)}</td>
|
<td>${escapeHtml(item.item_id)}</td>
|
||||||
<td title="${escapeHtml(item.item_title || '未设置')}">${escapeHtml(itemTitleDisplay)}</td>
|
<td title="${escapeHtml(item.item_title || '未设置')}">${escapeHtml(itemTitleDisplay)}</td>
|
||||||
<td title="${escapeHtml(item.item_detail || '未设置')}">${escapeHtml(itemDetailDisplay)}</td>
|
<td title="${escapeHtml(item.item_detail || '未设置')}">${escapeHtml(itemDetailDisplay)}</td>
|
||||||
|
<td>${multiSpecDisplay}</td>
|
||||||
<td>${formatDateTime(item.updated_at)}</td>
|
<td>${formatDateTime(item.updated_at)}</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="btn-group" role="group">
|
<div class="btn-group" role="group">
|
||||||
@ -6532,6 +6840,9 @@
|
|||||||
<button class="btn btn-sm btn-outline-danger" onclick="deleteItem('${escapeHtml(item.cookie_id)}', '${escapeHtml(item.item_id)}', '${escapeHtml(item.item_title || item.item_id)}')" title="删除">
|
<button class="btn btn-sm btn-outline-danger" onclick="deleteItem('${escapeHtml(item.cookie_id)}', '${escapeHtml(item.item_id)}', '${escapeHtml(item.item_title || item.item_id)}')" title="删除">
|
||||||
<i class="bi bi-trash"></i>
|
<i class="bi bi-trash"></i>
|
||||||
</button>
|
</button>
|
||||||
|
<button class="btn btn-sm ${isMultiSpec ? 'btn-warning' : 'btn-success'}" onclick="toggleItemMultiSpec('${escapeHtml(item.cookie_id)}', '${escapeHtml(item.item_id)}', ${!isMultiSpec})" title="${isMultiSpec ? '关闭多规格' : '开启多规格'}">
|
||||||
|
<i class="bi ${isMultiSpec ? 'bi-toggle-on' : 'bi-toggle-off'}"></i>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user