自动回复增加人工接入后等待时间

This commit is contained in:
zhinianboke 2025-08-05 16:11:27 +08:00
parent d53a114dba
commit aae9d1ab46
7 changed files with 683 additions and 14 deletions

121
AUTO_REPLY_PAUSE_README.md Normal file
View File

@ -0,0 +1,121 @@
# 自动回复暂停功能说明
## 功能概述
当系统检测到某个 `chat_id` 有手动发出的消息时,会自动暂停该 `chat_id` 的自动回复功能10分钟。如果在暂停期间再次检测到手动发出的消息会重新开始计时10分钟。
## 功能特性
### 1. 智能检测
- 系统会自动检测每个聊天会话中的手动发出消息
- 检测到手动发出时会在日志中显示:`[时间] 【手动发出】 商品(商品ID): 消息内容`
### 2. 自动暂停
- 检测到手动发出后,立即暂停该 `chat_id` 的自动回复10分钟
- 暂停期间会在日志中显示:`【账号ID】检测到手动发出消息chat_id XXX 自动回复暂停10分钟恢复时间: YYYY-MM-DD HH:MM:SS`
### 3. 重新计时
- 如果在暂停期间再次检测到手动发出会重新开始计时10分钟
- 每次手动发出都会刷新暂停时间
### 4. 暂停提示
- 当收到消息但处于暂停状态时,会在日志中显示:
`【账号ID】【系统】chat_id XXX 自动回复已暂停,剩余时间: X分Y秒`
### 5. 自动恢复
- 暂停时间到期后,自动恢复该 `chat_id` 的自动回复功能
- 无需手动干预
## 技术实现
### 核心组件
#### AutoReplyPauseManager 类
- `pause_chat(chat_id, cookie_id)`: 暂停指定chat_id的自动回复
- `is_chat_paused(chat_id)`: 检查指定chat_id是否处于暂停状态
- `get_remaining_pause_time(chat_id)`: 获取剩余暂停时间
- `cleanup_expired_pauses()`: 清理过期的暂停记录
#### 集成点
1. **检测手动发出** (第2730行)
```python
if send_user_id == self.myid:
logger.info(f"[{msg_time}] 【手动发出】 商品({item_id}): {send_message}")
# 暂停该chat_id的自动回复10分钟
pause_manager.pause_chat(chat_id, self.cookie_id)
return
```
2. **检查暂停状态** (第2750行)
```python
# 检查该chat_id是否处于暂停状态
if pause_manager.is_chat_paused(chat_id):
remaining_time = pause_manager.get_remaining_pause_time(chat_id)
remaining_minutes = remaining_time // 60
remaining_seconds = remaining_time % 60
logger.info(f"[{msg_time}] 【{self.cookie_id}】【系统】chat_id {chat_id} 自动回复已暂停,剩余时间: {remaining_minutes}分{remaining_seconds}秒")
return
```
3. **定期清理** (第2372行)
```python
async def pause_cleanup_loop(self):
"""定期清理过期的暂停记录"""
while True:
# 每5分钟清理一次过期记录
pause_manager.cleanup_expired_pauses()
await asyncio.sleep(300)
```
## 配置参数
### 暂停时长
- 默认10分钟 (600秒)
- 位置:`AutoReplyPauseManager.__init__()` 中的 `self.pause_duration`
- 可根据需要修改
### 清理频率
- 默认每5分钟清理一次过期记录
- 位置:`pause_cleanup_loop()` 中的 `await asyncio.sleep(300)`
## 日志示例
### 检测到手动发出
```
2025-08-05 14:48:32.209 | INFO | XianyuAutoAsync:handle_message:2673 - [2025-08-05 14:48:31] 【手动发出】 商品(12345): 你好,这个商品还在吗?
2025-08-05 14:48:32.210 | INFO | XianyuAutoAsync:pause_chat:40 - 【dfg】检测到手动发出消息chat_id chat_123 自动回复暂停10分钟恢复时间: 2025-08-05 14:58:32
```
### 暂停期间收到消息
```
2025-08-05 14:50:15.123 | INFO | XianyuAutoAsync:handle_message:2678 - [2025-08-05 14:50:15] 【收到】用户: 张三 (ID: 67890), 商品(12345): 多少钱?
2025-08-05 14:50:15.124 | INFO | XianyuAutoAsync:handle_message:2754 - [2025-08-05 14:50:15] 【dfg】【系统】chat_id chat_123 自动回复已暂停,剩余时间: 8分17秒
```
### 重新计时
```
2025-08-05 14:55:20.456 | INFO | XianyuAutoAsync:handle_message:2673 - [2025-08-05 14:55:20] 【手动发出】 商品(12345): 价格可以商量
2025-08-05 14:55:20.457 | INFO | XianyuAutoAsync:pause_chat:40 - 【dfg】检测到手动发出消息chat_id chat_123 自动回复暂停10分钟恢复时间: 2025-08-05 15:05:20
```
## 注意事项
1. **全局管理器**: 使用全局的 `pause_manager` 实例,所有账号共享暂停状态
2. **内存存储**: 暂停记录存储在内存中,程序重启后会丢失
3. **自动清理**: 系统会定期清理过期的暂停记录,避免内存泄漏
4. **线程安全**: 暂停管理器是线程安全的,可以在多个协程中使用
## 测试
运行测试脚本验证功能:
```bash
python test_pause_manager.py
```
测试包括:
- 基本暂停/恢复功能
- 重新计时机制
- 多chat_id管理
- 过期清理功能
- 时间计算准确性

185
PAUSE_DURATION_FEATURE.md Normal file
View File

@ -0,0 +1,185 @@
# 账号自动回复暂停时间配置功能
## 功能概述
为每个账号单独配置自动回复暂停时间,当检测到手动发出消息后,该账号的自动回复会暂停指定的时间长度。
## 功能特性
### 1. 个性化配置
- 每个账号可以单独设置暂停时间1-60分钟
- 默认暂停时间为10分钟
- 支持实时修改,立即生效
### 2. 直观界面
- 在账号管理表格中新增"暂停时间"列
- 点击暂停时间可直接编辑
- 带有说明工具提示,解释功能作用
### 3. 智能验证
- 暂停时间范围限制1-60分钟
- 输入验证和错误提示
- 支持键盘操作Enter保存Escape取消
## 界面展示
### 表格列头
```
账号ID | Cookie值 | 关键词 | 状态 | 默认回复 | AI回复 | 自动确认发货 | 备注 | 暂停时间 | 操作
```
### 暂停时间列
- 显示格式:`🕐 10分钟`
- 工具提示:`检测到手动发出消息后,自动回复暂停的时间长度(分钟)。如果在暂停期间再次手动发出消息,会重新开始计时。`
- 点击可编辑,支持数字输入框
## 技术实现
### 数据库结构
```sql
-- cookies表新增字段
ALTER TABLE cookies ADD COLUMN pause_duration INTEGER DEFAULT 10;
```
### 后端API
#### 1. 更新暂停时间
```http
PUT /cookies/{cid}/pause-duration
Content-Type: application/json
Authorization: Bearer {token}
{
"pause_duration": 15
}
```
**响应**
```json
{
"message": "暂停时间更新成功",
"pause_duration": 15
}
```
#### 2. 获取暂停时间
```http
GET /cookies/{cid}/pause-duration
Authorization: Bearer {token}
```
**响应**
```json
{
"pause_duration": 15,
"message": "获取暂停时间成功"
}
```
### 前端功能
#### 1. 表格显示
- 在账号列表中显示每个账号的暂停时间
- 默认显示为"🕐 10分钟"格式
#### 2. 内联编辑
```javascript
function editPauseDuration(cookieId, currentDuration) {
// 创建数字输入框
// 支持1-60分钟范围
// Enter保存Escape取消
// 实时验证和错误提示
}
```
#### 3. 工具提示
- 使用Bootstrap Tooltip组件
- 自动初始化和重新初始化
### 暂停管理器集成
#### 动态获取暂停时间
```python
def pause_chat(self, chat_id: str, cookie_id: str):
"""暂停指定chat_id的自动回复使用账号特定的暂停时间"""
# 获取账号特定的暂停时间
try:
from db_manager import db_manager
pause_minutes = db_manager.get_cookie_pause_duration(cookie_id)
except Exception as e:
logger.error(f"获取账号 {cookie_id} 暂停时间失败: {e}使用默认10分钟")
pause_minutes = 10
pause_duration_seconds = pause_minutes * 60
pause_until = time.time() + pause_duration_seconds
self.paused_chats[chat_id] = pause_until
# 记录日志
end_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(pause_until))
logger.info(f"【{cookie_id}】检测到手动发出消息chat_id {chat_id} 自动回复暂停{pause_minutes}分钟,恢复时间: {end_time}")
```
## 使用流程
### 1. 查看当前设置
1. 登录系统,进入账号管理页面
2. 在表格中查看"暂停时间"列
3. 鼠标悬停在列头的问号图标上查看功能说明
### 2. 修改暂停时间
1. 点击要修改的账号的暂停时间(如"🕐 10分钟"
2. 输入框出现输入新的暂停时间1-60分钟
3. 按Enter键保存或按Escape键取消
4. 系统显示成功提示
### 3. 验证生效
1. 修改后的设置立即生效
2. 下次该账号检测到手动发出消息时,会使用新的暂停时间
3. 在日志中可以看到使用的暂停时间
## 日志示例
### 使用自定义暂停时间
```
2025-08-05 15:30:15.123 | INFO | XianyuAutoAsync:pause_chat:49 - 【abc123】检测到手动发出消息chat_id chat_456 自动回复暂停15分钟恢复时间: 2025-08-05 15:45:15
```
### 获取暂停时间失败时使用默认值
```
2025-08-05 15:30:15.124 | ERROR | XianyuAutoAsync:pause_chat:42 - 获取账号 abc123 暂停时间失败: Database error使用默认10分钟
2025-08-05 15:30:15.125 | INFO | XianyuAutoAsync:pause_chat:49 - 【abc123】检测到手动发出消息chat_id chat_456 自动回复暂停10分钟恢复时间: 2025-08-05 15:40:15
```
## 配置建议
### 不同场景的推荐设置
1. **高频互动商品**5-10分钟
- 适用于需要快速响应的热门商品
- 减少客户等待时间
2. **普通商品**10-15分钟默认
- 平衡自动化和人工干预
- 适合大多数场景
3. **低频互动商品**20-30分钟
- 适用于不常有咨询的商品
- 给予更多人工处理时间
4. **特殊商品**30-60分钟
- 需要详细沟通的复杂商品
- 避免自动回复干扰深度交流
## 注意事项
1. **范围限制**暂停时间必须在1-60分钟之间
2. **立即生效**:修改后的设置立即生效,无需重启
3. **默认值**新账号默认使用10分钟暂停时间
4. **错误处理**获取暂停时间失败时自动使用默认10分钟
5. **权限控制**:只能修改自己账号的暂停时间设置
## 兼容性
- **向后兼容**现有账号自动获得默认10分钟设置
- **数据迁移**系统启动时自动添加pause_duration字段
- **API兼容**现有API继续正常工作新增字段可选

View File

@ -21,6 +21,70 @@ from utils.ws_utils import WebSocketClient
import sys
import aiohttp
class AutoReplyPauseManager:
"""自动回复暂停管理器"""
def __init__(self):
# 存储每个chat_id的暂停信息 {chat_id: pause_until_timestamp}
self.paused_chats = {}
def pause_chat(self, chat_id: str, cookie_id: str):
"""暂停指定chat_id的自动回复使用账号特定的暂停时间"""
# 获取账号特定的暂停时间
try:
from db_manager import db_manager
pause_minutes = db_manager.get_cookie_pause_duration(cookie_id)
except Exception as e:
logger.error(f"获取账号 {cookie_id} 暂停时间失败: {e}使用默认10分钟")
pause_minutes = 10
pause_duration_seconds = pause_minutes * 60
pause_until = time.time() + pause_duration_seconds
self.paused_chats[chat_id] = pause_until
# 计算暂停结束时间
end_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(pause_until))
logger.info(f"{cookie_id}】检测到手动发出消息chat_id {chat_id} 自动回复暂停{pause_minutes}分钟,恢复时间: {end_time}")
def is_chat_paused(self, chat_id: str) -> bool:
"""检查指定chat_id是否处于暂停状态"""
if chat_id not in self.paused_chats:
return False
current_time = time.time()
pause_until = self.paused_chats[chat_id]
if current_time >= pause_until:
# 暂停时间已过,移除记录
del self.paused_chats[chat_id]
return False
return True
def get_remaining_pause_time(self, chat_id: str) -> int:
"""获取指定chat_id剩余暂停时间"""
if chat_id not in self.paused_chats:
return 0
current_time = time.time()
pause_until = self.paused_chats[chat_id]
remaining = max(0, int(pause_until - current_time))
return remaining
def cleanup_expired_pauses(self):
"""清理已过期的暂停记录"""
current_time = time.time()
expired_chats = [chat_id for chat_id, pause_until in self.paused_chats.items()
if current_time >= pause_until]
for chat_id in expired_chats:
del self.paused_chats[chat_id]
# 全局暂停管理器实例
pause_manager = AutoReplyPauseManager()
# 日志配置
log_dir = 'logs'
os.makedirs(log_dir, exist_ok=True)
@ -110,6 +174,9 @@ class XianyuLive:
self.session = None # 用于API调用的aiohttp session
# 启动定期清理过期暂停记录的任务
self.cleanup_task = None
def is_auto_confirm_enabled(self) -> bool:
"""检查当前账号是否启用自动确认发货"""
try:
@ -2301,6 +2368,25 @@ class XianyuLive:
logger.error(f"处理心跳响应出错: {self._safe_str(e)}")
return False
async def pause_cleanup_loop(self):
"""定期清理过期的暂停记录"""
while True:
try:
# 检查账号是否启用
from cookie_manager import manager as cookie_manager
if cookie_manager and not cookie_manager.get_cookie_status(self.cookie_id):
logger.info(f"{self.cookie_id}】账号已禁用,停止暂停记录清理循环")
break
# 清理过期的暂停记录
pause_manager.cleanup_expired_pauses()
# 每5分钟清理一次
await asyncio.sleep(300)
except Exception as e:
logger.error(f"{self.cookie_id}】暂停记录清理失败: {self._safe_str(e)}")
await asyncio.sleep(300) # 出错后也等待5分钟再重试
async def send_msg_once(self, toid, item_id, text):
headers = {
"Cookie": self.cookies_str,
@ -2672,6 +2758,8 @@ class XianyuLive:
if send_user_id == self.myid:
logger.info(f"[{msg_time}] 【手动发出】 商品({item_id}): {send_message}")
# 暂停该chat_id的自动回复10分钟
pause_manager.pause_chat(chat_id, self.cookie_id)
return
else:
@ -2686,6 +2774,14 @@ class XianyuLive:
logger.info(f"[{msg_time}] 【{self.cookie_id}】【系统】自动回复已禁用")
return
# 检查该chat_id是否处于暂停状态
if pause_manager.is_chat_paused(chat_id):
remaining_time = pause_manager.get_remaining_pause_time(chat_id)
remaining_minutes = remaining_time // 60
remaining_seconds = remaining_time % 60
logger.info(f"[{msg_time}] 【{self.cookie_id}】【系统】chat_id {chat_id} 自动回复已暂停,剩余时间: {remaining_minutes}{remaining_seconds}")
return
# 构造用户URL
user_url = f'https://www.goofish.com/personal?userId={send_user_id}'
@ -2878,6 +2974,11 @@ class XianyuLive:
logger.info(f"{self.cookie_id}】启动token刷新任务...")
self.token_refresh_task = asyncio.create_task(self.token_refresh_loop())
# 启动暂停记录清理任务
if not self.cleanup_task:
logger.info(f"{self.cookie_id}】启动暂停记录清理任务...")
self.cleanup_task = asyncio.create_task(self.pause_cleanup_loop())
logger.info(f"{self.cookie_id}】开始监听WebSocket消息...")
async for message in websocket:
@ -2901,9 +3002,18 @@ class XianyuLive:
self.heartbeat_task.cancel()
if self.token_refresh_task:
self.token_refresh_task.cancel()
if self.cleanup_task:
self.cleanup_task.cancel()
await asyncio.sleep(5) # 等待5秒后重试
continue
finally:
# 清理所有任务
if self.heartbeat_task:
self.heartbeat_task.cancel()
if self.token_refresh_task:
self.token_refresh_task.cancel()
if self.cleanup_task:
self.cleanup_task.cancel()
await self.close_session() # 确保关闭session
async def get_item_list_info(self, page_number=1, page_size=20, retry_count=0):

View File

@ -114,6 +114,7 @@ class DBManager:
user_id INTEGER NOT NULL,
auto_confirm INTEGER DEFAULT 1,
remark TEXT DEFAULT '',
pause_duration INTEGER DEFAULT 10,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
)
@ -383,6 +384,12 @@ class DBManager:
cursor.execute("ALTER TABLE cookies ADD COLUMN remark TEXT DEFAULT ''")
logger.info("数据库迁移完成添加remark列")
# 检查cookies表是否存在pause_duration列
if 'pause_duration' not in cookie_columns:
logger.info("添加cookies表的pause_duration列...")
cursor.execute("ALTER TABLE cookies ADD COLUMN pause_duration INTEGER DEFAULT 10")
logger.info("数据库迁移完成添加pause_duration列")
except Exception as e:
logger.error(f"数据库迁移失败: {e}")
# 迁移失败不应该阻止程序启动
@ -1088,11 +1095,11 @@ class DBManager:
return None
def get_cookie_details(self, cookie_id: str) -> Optional[Dict[str, any]]:
"""获取Cookie的详细信息包括user_id、auto_confirm和remark"""
"""获取Cookie的详细信息包括user_id、auto_confirm、remark和pause_duration"""
with self.lock:
try:
cursor = self.conn.cursor()
self._execute_sql(cursor, "SELECT id, value, user_id, auto_confirm, remark, created_at FROM cookies WHERE id = ?", (cookie_id,))
self._execute_sql(cursor, "SELECT id, value, user_id, auto_confirm, remark, pause_duration, created_at FROM cookies WHERE id = ?", (cookie_id,))
result = cursor.fetchone()
if result:
return {
@ -1101,7 +1108,8 @@ class DBManager:
'user_id': result[2],
'auto_confirm': bool(result[3]),
'remark': result[4] or '',
'created_at': result[5]
'pause_duration': result[5] or 10,
'created_at': result[6]
}
return None
except Exception as e:
@ -1134,6 +1142,33 @@ class DBManager:
logger.error(f"更新账号备注失败: {e}")
return False
def update_cookie_pause_duration(self, cookie_id: str, pause_duration: int) -> bool:
"""更新Cookie的自动回复暂停时间"""
with self.lock:
try:
cursor = self.conn.cursor()
self._execute_sql(cursor, "UPDATE cookies SET pause_duration = ? WHERE id = ?", (pause_duration, cookie_id))
self.conn.commit()
logger.info(f"更新账号 {cookie_id} 自动回复暂停时间: {pause_duration}分钟")
return True
except Exception as e:
logger.error(f"更新账号自动回复暂停时间失败: {e}")
return False
def get_cookie_pause_duration(self, cookie_id: str) -> int:
"""获取Cookie的自动回复暂停时间"""
with self.lock:
try:
cursor = self.conn.cursor()
self._execute_sql(cursor, "SELECT pause_duration FROM cookies WHERE id = ?", (cookie_id,))
result = cursor.fetchone()
if result:
return result[0] or 10 # 默认10分钟
return 10 # 如果没有找到记录,返回默认值
except Exception as e:
logger.error(f"获取账号自动回复暂停时间失败: {e}")
return 10 # 出错时返回默认值
def get_auto_confirm(self, cookie_id: str) -> bool:
"""获取Cookie的自动确认发货设置"""
with self.lock:

View File

@ -962,7 +962,8 @@ def get_cookies_details(current_user: Dict[str, Any] = Depends(get_current_user)
'value': cookie_value,
'enabled': cookie_enabled,
'auto_confirm': auto_confirm,
'remark': remark
'remark': remark,
'pause_duration': cookie_details.get('pause_duration', 10) if cookie_details else 10
})
return result
@ -1557,6 +1558,10 @@ class RemarkUpdate(BaseModel):
remark: str
class PauseDurationUpdate(BaseModel):
pause_duration: int
@app.put("/cookies/{cid}/auto-confirm")
def update_auto_confirm(cid: str, update_data: AutoConfirmUpdate, current_user: Dict[str, Any] = Depends(get_current_user)):
"""更新账号的自动确认发货设置"""
@ -1676,6 +1681,65 @@ def get_cookie_remark(cid: str, current_user: Dict[str, Any] = Depends(get_curre
raise HTTPException(status_code=500, detail=str(e))
@app.put("/cookies/{cid}/pause-duration")
def update_cookie_pause_duration(cid: str, update_data: PauseDurationUpdate, current_user: Dict[str, Any] = Depends(get_current_user)):
"""更新账号自动回复暂停时间"""
if cookie_manager.manager is None:
raise HTTPException(status_code=500, detail="CookieManager 未就绪")
try:
# 检查cookie是否属于当前用户
user_id = current_user['user_id']
from db_manager import db_manager
user_cookies = db_manager.get_all_cookies(user_id)
if cid not in user_cookies:
raise HTTPException(status_code=403, detail="无权限操作该Cookie")
# 验证暂停时间范围1-60分钟
if not (1 <= update_data.pause_duration <= 60):
raise HTTPException(status_code=400, detail="暂停时间必须在1-60分钟之间")
# 更新暂停时间
success = db_manager.update_cookie_pause_duration(cid, update_data.pause_duration)
if success:
log_with_user('info', f"更新账号自动回复暂停时间: {cid} -> {update_data.pause_duration}分钟", current_user)
return {
"message": "暂停时间更新成功",
"pause_duration": update_data.pause_duration
}
else:
raise HTTPException(status_code=500, detail="暂停时间更新失败")
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.get("/cookies/{cid}/pause-duration")
def get_cookie_pause_duration(cid: str, current_user: Dict[str, Any] = Depends(get_current_user)):
"""获取账号自动回复暂停时间"""
if cookie_manager.manager is None:
raise HTTPException(status_code=500, detail="CookieManager 未就绪")
try:
# 检查cookie是否属于当前用户
user_id = current_user['user_id']
from db_manager import db_manager
user_cookies = db_manager.get_all_cookies(user_id)
if cid not in user_cookies:
raise HTTPException(status_code=403, detail="无权限操作该Cookie")
# 获取暂停时间
pause_duration = db_manager.get_cookie_pause_duration(cid)
return {
"pause_duration": pause_duration,
"message": "获取暂停时间成功"
}
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))

View File

@ -277,14 +277,21 @@
<table class="table table-hover" id="cookieTable">
<thead>
<tr>
<th style="width: 10%">账号ID</th>
<th style="width: 16%">Cookie值</th>
<th style="width: 8%">关键词</th>
<th style="width: 8%">状态</th>
<th style="width: 9%">默认回复</th>
<th style="width: 9%">AI回复</th>
<th style="width: 10%">自动确认发货</th>
<th style="width: 12%">备注</th>
<th style="width: 9%">账号ID</th>
<th style="width: 14%">Cookie值</th>
<th style="width: 7%">关键词</th>
<th style="width: 7%">状态</th>
<th style="width: 8%">默认回复</th>
<th style="width: 8%">AI回复</th>
<th style="width: 9%">自动确认发货</th>
<th style="width: 10%">备注</th>
<th style="width: 10%">
暂停时间
<i class="bi bi-question-circle ms-1"
data-bs-toggle="tooltip"
data-bs-placement="top"
title="检测到手动发出消息后,自动回复暂停的时间长度(分钟)。如果在暂停期间再次手动发出消息,会重新开始计时。"></i>
</th>
<th style="width: 18%">操作</th>
</tr>
</thead>

View File

@ -1075,7 +1075,7 @@ async function loadCookies() {
if (cookieDetails.length === 0) {
tbody.innerHTML = `
<tr>
<td colspan="9" class="text-center py-4 text-muted empty-state">
<td colspan="10" class="text-center py-4 text-muted empty-state">
<i class="bi bi-inbox fs-1 d-block mb-3"></i>
<h5>暂无账号</h5>
<p class="mb-0">请添加新的闲鱼账号开始使用</p>
@ -1209,6 +1209,13 @@ async function loadCookies() {
</span>
</div>
</td>
<td class="align-middle">
<div class="pause-duration-cell" data-cookie-id="${cookie.id}">
<span class="pause-duration-display" onclick="editPauseDuration('${cookie.id}', ${cookie.pause_duration || 10})" title="点击编辑暂停时间" style="cursor: pointer; color: #6c757d; font-size: 0.875rem;">
<i class="bi bi-clock me-1"></i>${cookie.pause_duration || 10}
</span>
</div>
</td>
<td class="align-middle">
<div class="btn-group" role="group">
<button class="btn btn-sm btn-outline-primary" onclick="editCookieInline('${cookie.id}', '${cookie.value}')" title="修改Cookie" ${!isEnabled ? 'disabled' : ''}>
@ -1247,6 +1254,9 @@ async function loadCookies() {
});
});
// 重新初始化工具提示
initTooltips();
} catch (err) {
// 错误已在fetchJSON中处理
} finally {
@ -1780,6 +1790,9 @@ document.addEventListener('DOMContentLoaded', async () => {
// 初始化编辑卡券图片文件选择器
initEditCardImageFileSelector();
// 初始化工具提示
initTooltips();
// 点击侧边栏外部关闭移动端菜单
document.addEventListener('click', function(e) {
const sidebar = document.getElementById('sidebar');
@ -6483,6 +6496,140 @@ function editRemark(cookieId, currentRemark) {
input.select();
}
// 编辑暂停时间
function editPauseDuration(cookieId, currentDuration) {
console.log('editPauseDuration called:', cookieId, currentDuration); // 调试信息
const pauseCell = document.querySelector(`[data-cookie-id="${cookieId}"] .pause-duration-display`);
if (!pauseCell) {
console.log('pauseCell not found'); // 调试信息
return;
}
// 创建输入框
const input = document.createElement('input');
input.type = 'number';
input.className = 'form-control form-control-sm';
input.value = currentDuration || 10;
input.placeholder = '请输入暂停时间...';
input.style.fontSize = '0.875rem';
input.min = 1;
input.max = 60;
input.step = 1;
// 保存原始内容和原始值
const originalContent = pauseCell.innerHTML;
const originalValue = currentDuration || 10;
// 标记是否已经进行了编辑
let hasChanged = false;
let isProcessing = false; // 防止重复处理
// 替换为输入框
pauseCell.innerHTML = '';
pauseCell.appendChild(input);
// 监听输入变化
input.addEventListener('input', () => {
const newValue = parseInt(input.value) || 10;
hasChanged = newValue !== originalValue;
});
// 保存函数
const savePauseDuration = async () => {
console.log('savePauseDuration called, isProcessing:', isProcessing, 'hasChanged:', hasChanged); // 调试信息
if (isProcessing) return; // 防止重复调用
const newDuration = parseInt(input.value) || 10;
console.log('newDuration:', newDuration, 'originalValue:', originalValue); // 调试信息
// 验证范围
if (newDuration < 1 || newDuration > 60) {
showToast('暂停时间必须在1-60分钟之间', 'warning');
input.focus();
return;
}
// 如果没有变化,直接恢复显示
if (!hasChanged || newDuration === originalValue) {
console.log('No changes detected, restoring original content'); // 调试信息
pauseCell.innerHTML = originalContent;
return;
}
isProcessing = true;
try {
const response = await fetch(`${apiBase}/cookies/${cookieId}/pause-duration`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${authToken}`
},
body: JSON.stringify({ pause_duration: newDuration })
});
if (response.ok) {
// 更新显示
pauseCell.innerHTML = `
<span class="pause-duration-display" onclick="editPauseDuration('${cookieId}', ${newDuration})" title="点击编辑暂停时间" style="cursor: pointer; color: #6c757d; font-size: 0.875rem;">
<i class="bi bi-clock me-1"></i>${newDuration}
</span>
`;
showToast('暂停时间更新成功', 'success');
} else {
const errorData = await response.json();
showToast(`暂停时间更新失败: ${errorData.detail || '未知错误'}`, 'danger');
// 恢复原始内容
pauseCell.innerHTML = originalContent;
}
} catch (error) {
console.error('更新暂停时间失败:', error);
showToast('暂停时间更新失败', 'danger');
// 恢复原始内容
pauseCell.innerHTML = originalContent;
} finally {
isProcessing = false;
}
};
// 取消函数
const cancelEdit = () => {
if (isProcessing) return;
pauseCell.innerHTML = originalContent;
};
// 延迟绑定blur事件避免立即触发
setTimeout(() => {
input.addEventListener('blur', savePauseDuration);
}, 100);
// 绑定键盘事件
input.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
e.preventDefault();
savePauseDuration();
} else if (e.key === 'Escape') {
e.preventDefault();
cancelEdit();
}
});
// 聚焦并选中文本
input.focus();
input.select();
}
// ==================== 工具提示初始化 ====================
// 初始化工具提示
function initTooltips() {
// 初始化所有工具提示
const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
tooltipTriggerList.map(function (tooltipTriggerEl) {
return new bootstrap.Tooltip(tooltipTriggerEl);
});
}
// ==================== 系统设置功能 ====================
// 加载系统设置