mirror of
https://github.com/zhinianboke/xianyu-auto-reply.git
synced 2025-08-30 01:27:35 +08:00
新增商品搜索功能
This commit is contained in:
parent
0b9b81bc4e
commit
4f16a51087
@ -1,121 +0,0 @@
|
||||
# 自动回复暂停功能说明
|
||||
|
||||
## 功能概述
|
||||
|
||||
当系统检测到某个 `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管理
|
||||
- 过期清理功能
|
||||
- 时间计算准确性
|
@ -1,185 +0,0 @@
|
||||
# 账号自动回复暂停时间配置功能
|
||||
|
||||
## 功能概述
|
||||
|
||||
为每个账号单独配置自动回复暂停时间,当检测到手动发出消息后,该账号的自动回复会暂停指定的时间长度。
|
||||
|
||||
## 功能特性
|
||||
|
||||
### 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继续正常工作,新增字段可选
|
@ -3658,9 +3658,10 @@ def get_table_data(table_name: str, admin_user: Dict[str, Any] = Depends(require
|
||||
|
||||
# 验证表名安全性
|
||||
allowed_tables = [
|
||||
'users', 'cookies', 'keywords', 'default_replies', 'ai_reply_settings',
|
||||
'users', 'cookies', 'cookie_status', 'keywords', 'default_replies', 'default_reply_records',
|
||||
'ai_reply_settings', 'ai_conversations', 'ai_item_cache', 'item_info',
|
||||
'message_notifications', 'cards', 'delivery_rules', 'notification_channels',
|
||||
'user_settings', 'email_verifications', 'captcha_codes'
|
||||
'user_settings', 'system_settings', 'email_verifications', 'captcha_codes'
|
||||
]
|
||||
|
||||
if table_name not in allowed_tables:
|
||||
@ -3694,9 +3695,10 @@ def delete_table_record(table_name: str, record_id: str, admin_user: Dict[str, A
|
||||
|
||||
# 验证表名安全性
|
||||
allowed_tables = [
|
||||
'users', 'cookies', 'keywords', 'default_replies', 'ai_reply_settings',
|
||||
'users', 'cookies', 'cookie_status', 'keywords', 'default_replies', 'default_reply_records',
|
||||
'ai_reply_settings', 'ai_conversations', 'ai_item_cache', 'item_info',
|
||||
'message_notifications', 'cards', 'delivery_rules', 'notification_channels',
|
||||
'user_settings', 'email_verifications', 'captcha_codes'
|
||||
'user_settings', 'system_settings', 'email_verifications', 'captcha_codes'
|
||||
]
|
||||
|
||||
if table_name not in allowed_tables:
|
||||
@ -3733,9 +3735,10 @@ def clear_table_data(table_name: str, admin_user: Dict[str, Any] = Depends(requi
|
||||
|
||||
# 验证表名安全性
|
||||
allowed_tables = [
|
||||
'cookies', 'keywords', 'default_replies', 'ai_reply_settings',
|
||||
'cookies', 'cookie_status', 'keywords', 'default_replies', 'default_reply_records',
|
||||
'ai_reply_settings', 'ai_conversations', 'ai_item_cache', 'item_info',
|
||||
'message_notifications', 'cards', 'delivery_rules', 'notification_channels',
|
||||
'user_settings', 'email_verifications', 'captcha_codes'
|
||||
'user_settings', 'system_settings', 'email_verifications', 'captcha_codes'
|
||||
]
|
||||
|
||||
# 不允许清空用户表
|
||||
|
@ -95,14 +95,20 @@
|
||||
<option value="">请选择数据表...</option>
|
||||
<option value="users">users - 用户表</option>
|
||||
<option value="cookies">cookies - Cookie账号表</option>
|
||||
<option value="cookie_status">cookie_status - Cookie状态表</option>
|
||||
<option value="keywords">keywords - 关键字表</option>
|
||||
<option value="default_replies">default_replies - 默认回复表</option>
|
||||
<option value="default_reply_records">default_reply_records - 默认回复记录表</option>
|
||||
<option value="ai_reply_settings">ai_reply_settings - AI回复设置表</option>
|
||||
<option value="ai_conversations">ai_conversations - AI对话历史表</option>
|
||||
<option value="ai_item_cache">ai_item_cache - AI商品信息缓存表</option>
|
||||
<option value="item_info">item_info - 商品信息表</option>
|
||||
<option value="message_notifications">message_notifications - 消息通知表</option>
|
||||
<option value="cards">cards - 卡券表</option>
|
||||
<option value="delivery_rules">delivery_rules - 发货规则表</option>
|
||||
<option value="notification_channels">notification_channels - 通知渠道表</option>
|
||||
<option value="user_settings">user_settings - 用户设置表</option>
|
||||
<option value="system_settings">system_settings - 系统设置表</option>
|
||||
<option value="email_verifications">email_verifications - 邮箱验证表</option>
|
||||
<option value="captcha_codes">captcha_codes - 验证码表</option>
|
||||
</select>
|
||||
@ -254,14 +260,20 @@
|
||||
const tableDescriptions = {
|
||||
'users': '用户表',
|
||||
'cookies': 'Cookie账号表',
|
||||
'cookie_status': 'Cookie状态表',
|
||||
'keywords': '关键字表',
|
||||
'default_replies': '默认回复表',
|
||||
'default_reply_records': '默认回复记录表',
|
||||
'ai_reply_settings': 'AI回复设置表',
|
||||
'ai_conversations': 'AI对话历史表',
|
||||
'ai_item_cache': 'AI商品信息缓存表',
|
||||
'item_info': '商品信息表',
|
||||
'message_notifications': '消息通知表',
|
||||
'cards': '卡券表',
|
||||
'delivery_rules': '发货规则表',
|
||||
'notification_channels': '通知渠道表',
|
||||
'user_settings': '用户设置表',
|
||||
'system_settings': '系统设置表',
|
||||
'email_verifications': '邮箱验证表',
|
||||
'captcha_codes': '验证码表'
|
||||
};
|
||||
|
@ -314,16 +314,17 @@
|
||||
</div>
|
||||
|
||||
<div class="content-body">
|
||||
<!-- Cookie筛选 -->
|
||||
<!-- 筛选和搜索 -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-body">
|
||||
<div class="row align-items-end">
|
||||
<div class="row align-items-end mb-3">
|
||||
<div class="col-md-6">
|
||||
<label for="itemCookieFilter" class="form-label">筛选账号</label>
|
||||
<select class="form-select" id="itemCookieFilter" onchange="loadItemsByCookie()">
|
||||
<option value="">所有账号</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="d-flex justify-content-end align-items-end gap-2">
|
||||
<!-- 页码输入 -->
|
||||
@ -345,18 +346,33 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 搜索结果统计 -->
|
||||
<div id="itemSearchStats" class="text-muted small" style="display: none;">
|
||||
<i class="bi bi-search me-1"></i>
|
||||
<span id="itemSearchStatsText"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 商品列表 -->
|
||||
<div class="card">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<div class="d-flex align-items-center gap-3">
|
||||
<input type="text" class="form-control" id="itemSearchInput"
|
||||
placeholder="搜索商品标题或详情..." style="width: 300px;">
|
||||
<h5 class="mb-0">商品列表(自动发货根据商品标题和商品详情匹配关键字)</h5>
|
||||
</div>
|
||||
<button class="btn btn-sm btn-outline-danger" onclick="batchDeleteItems()" id="batchDeleteBtn" disabled>
|
||||
<i class="bi bi-trash"></i> 批量删除
|
||||
</button>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<!-- 搜索统计信息 -->
|
||||
<div id="itemSearchStats" class="text-muted small mb-2" style="display: none;">
|
||||
<i class="bi bi-search me-1"></i>
|
||||
<span id="itemSearchStatsText"></span>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
@ -375,12 +391,54 @@
|
||||
</thead>
|
||||
<tbody id="itemsTableBody">
|
||||
<tr>
|
||||
<td colspan="7" class="text-center text-muted">加载中...</td>
|
||||
<td colspan="8" class="text-center text-muted">加载中...</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 分页控件 -->
|
||||
<div class="card-footer" id="itemsPagination">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div class="text-muted small">
|
||||
<span id="itemsPageInfo">显示第 1-10 条,共 0 条记录</span>
|
||||
</div>
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<div class="btn-group" role="group">
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm" id="itemsFirstPage" onclick="goToItemsPage(1)" disabled>
|
||||
<i class="bi bi-chevron-double-left"></i>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm" id="itemsPrevPage" onclick="goToItemsPage(currentItemsPage - 1)" disabled>
|
||||
<i class="bi bi-chevron-left"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="d-flex align-items-center gap-2 mx-2">
|
||||
<span class="text-muted small">第</span>
|
||||
<input type="number" class="form-control form-control-sm" id="itemsPageInput"
|
||||
style="width: 60px;" min="1" value="1">
|
||||
<span class="text-muted small">页,共 <span id="itemsTotalPages">0</span> 页</span>
|
||||
</div>
|
||||
|
||||
<div class="btn-group" role="group">
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm" id="itemsNextPage" onclick="goToItemsPage(currentItemsPage + 1)" disabled>
|
||||
<i class="bi bi-chevron-right"></i>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm" id="itemsLastPage" onclick="goToItemsPage(totalItemsPages)" disabled>
|
||||
<i class="bi bi-chevron-double-right"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<select class="form-select form-select-sm ms-2" id="itemsPageSize" style="width: auto;">
|
||||
<option value="10">10条/页</option>
|
||||
<option value="20" selected>20条/页</option>
|
||||
<option value="50">50条/页</option>
|
||||
<option value="100">100条/页</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
254
static/js/app.js
254
static/js/app.js
@ -17,6 +17,14 @@ let accountKeywordCache = {};
|
||||
let cacheTimestamp = 0;
|
||||
const CACHE_DURATION = 30000; // 30秒缓存
|
||||
|
||||
// 商品列表搜索和分页相关变量
|
||||
let allItemsData = []; // 存储所有商品数据
|
||||
let filteredItemsData = []; // 存储过滤后的商品数据
|
||||
let currentItemsPage = 1; // 当前页码
|
||||
let itemsPerPage = 20; // 每页显示数量
|
||||
let totalItemsPages = 0; // 总页数
|
||||
let currentSearchKeyword = ''; // 当前搜索关键词
|
||||
|
||||
// ================================
|
||||
// 通用功能 - 菜单切换和导航
|
||||
// ================================
|
||||
@ -1793,6 +1801,9 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||
// 初始化工具提示
|
||||
initTooltips();
|
||||
|
||||
// 初始化商品搜索功能
|
||||
initItemsSearch();
|
||||
|
||||
// 点击侧边栏外部关闭移动端菜单
|
||||
document.addEventListener('click', function(e) {
|
||||
const sidebar = document.getElementById('sidebar');
|
||||
@ -4977,21 +4988,76 @@ async function loadItemsByCookie() {
|
||||
|
||||
// 显示商品列表
|
||||
function displayItems(items) {
|
||||
// 存储所有商品数据
|
||||
allItemsData = items || [];
|
||||
|
||||
// 应用搜索过滤
|
||||
applyItemsFilter();
|
||||
|
||||
// 显示当前页数据
|
||||
displayCurrentPageItems();
|
||||
|
||||
// 更新分页控件
|
||||
updateItemsPagination();
|
||||
}
|
||||
|
||||
// 应用搜索过滤
|
||||
function applyItemsFilter() {
|
||||
const searchKeyword = currentSearchKeyword.toLowerCase().trim();
|
||||
|
||||
if (!searchKeyword) {
|
||||
filteredItemsData = [...allItemsData];
|
||||
} else {
|
||||
filteredItemsData = allItemsData.filter(item => {
|
||||
const title = (item.item_title || '').toLowerCase();
|
||||
const detail = getItemDetailText(item.item_detail || '').toLowerCase();
|
||||
return title.includes(searchKeyword) || detail.includes(searchKeyword);
|
||||
});
|
||||
}
|
||||
|
||||
// 重置到第一页
|
||||
currentItemsPage = 1;
|
||||
|
||||
// 计算总页数
|
||||
totalItemsPages = Math.ceil(filteredItemsData.length / itemsPerPage);
|
||||
|
||||
// 更新搜索统计
|
||||
updateItemsSearchStats();
|
||||
}
|
||||
|
||||
// 获取商品详情的纯文本内容
|
||||
function getItemDetailText(itemDetail) {
|
||||
if (!itemDetail) return '';
|
||||
|
||||
try {
|
||||
// 尝试解析JSON
|
||||
const detail = JSON.parse(itemDetail);
|
||||
if (detail.content) {
|
||||
return detail.content;
|
||||
}
|
||||
return itemDetail;
|
||||
} catch (e) {
|
||||
// 如果不是JSON格式,直接返回原文本
|
||||
return itemDetail;
|
||||
}
|
||||
}
|
||||
|
||||
// 显示当前页的商品数据
|
||||
function displayCurrentPageItems() {
|
||||
const tbody = document.getElementById('itemsTableBody');
|
||||
|
||||
if (!items || items.length === 0) {
|
||||
if (!filteredItemsData || filteredItemsData.length === 0) {
|
||||
tbody.innerHTML = '<tr><td colspan="8" class="text-center text-muted">暂无商品数据</td></tr>';
|
||||
// 重置选择状态
|
||||
const selectAllCheckbox = document.getElementById('selectAllItems');
|
||||
if (selectAllCheckbox) {
|
||||
selectAllCheckbox.checked = false;
|
||||
selectAllCheckbox.indeterminate = false;
|
||||
}
|
||||
updateBatchDeleteButton();
|
||||
resetItemsSelection();
|
||||
return;
|
||||
}
|
||||
|
||||
const itemsHtml = items.map(item => {
|
||||
// 计算当前页的数据范围
|
||||
const startIndex = (currentItemsPage - 1) * itemsPerPage;
|
||||
const endIndex = startIndex + itemsPerPage;
|
||||
const currentPageItems = filteredItemsData.slice(startIndex, endIndex);
|
||||
|
||||
const itemsHtml = currentPageItems.map(item => {
|
||||
// 处理商品标题显示
|
||||
let itemTitleDisplay = item.item_title || '未设置';
|
||||
if (itemTitleDisplay.length > 30) {
|
||||
@ -5001,19 +5067,8 @@ function displayItems(items) {
|
||||
// 处理商品详情显示
|
||||
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 ? '...' : '');
|
||||
}
|
||||
const detailText = getItemDetailText(item.item_detail);
|
||||
itemDetailDisplay = detailText.substring(0, 50) + (detailText.length > 50 ? '...' : '');
|
||||
}
|
||||
|
||||
// 多规格状态显示
|
||||
@ -5033,7 +5088,7 @@ function displayItems(items) {
|
||||
<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(getItemDetailText(item.item_detail || ''))}">${escapeHtml(itemDetailDisplay)}</td>
|
||||
<td>${multiSpecDisplay}</td>
|
||||
<td>${formatDateTime(item.updated_at)}</td>
|
||||
<td>
|
||||
@ -5057,6 +5112,11 @@ function displayItems(items) {
|
||||
tbody.innerHTML = itemsHtml;
|
||||
|
||||
// 重置选择状态
|
||||
resetItemsSelection();
|
||||
}
|
||||
|
||||
// 重置商品选择状态
|
||||
function resetItemsSelection() {
|
||||
const selectAllCheckbox = document.getElementById('selectAllItems');
|
||||
if (selectAllCheckbox) {
|
||||
selectAllCheckbox.checked = false;
|
||||
@ -5065,6 +5125,154 @@ function displayItems(items) {
|
||||
updateBatchDeleteButton();
|
||||
}
|
||||
|
||||
// 商品搜索过滤函数
|
||||
function filterItems() {
|
||||
const searchInput = document.getElementById('itemSearchInput');
|
||||
currentSearchKeyword = searchInput ? searchInput.value : '';
|
||||
|
||||
// 应用过滤
|
||||
applyItemsFilter();
|
||||
|
||||
// 显示当前页数据
|
||||
displayCurrentPageItems();
|
||||
|
||||
// 更新分页控件
|
||||
updateItemsPagination();
|
||||
}
|
||||
|
||||
// 更新搜索统计信息
|
||||
function updateItemsSearchStats() {
|
||||
const statsElement = document.getElementById('itemSearchStats');
|
||||
const statsTextElement = document.getElementById('itemSearchStatsText');
|
||||
|
||||
if (!statsElement || !statsTextElement) return;
|
||||
|
||||
if (currentSearchKeyword) {
|
||||
statsTextElement.textContent = `搜索"${currentSearchKeyword}",找到 ${filteredItemsData.length} 个商品`;
|
||||
statsElement.style.display = 'block';
|
||||
} else {
|
||||
statsElement.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// 更新分页控件
|
||||
function updateItemsPagination() {
|
||||
const paginationElement = document.getElementById('itemsPagination');
|
||||
const pageInfoElement = document.getElementById('itemsPageInfo');
|
||||
const totalPagesElement = document.getElementById('itemsTotalPages');
|
||||
const pageInputElement = document.getElementById('itemsPageInput');
|
||||
|
||||
if (!paginationElement) return;
|
||||
|
||||
// 分页控件总是显示
|
||||
paginationElement.style.display = 'block';
|
||||
|
||||
// 更新页面信息
|
||||
const startIndex = (currentItemsPage - 1) * itemsPerPage + 1;
|
||||
const endIndex = Math.min(currentItemsPage * itemsPerPage, filteredItemsData.length);
|
||||
|
||||
if (pageInfoElement) {
|
||||
pageInfoElement.textContent = `显示第 ${startIndex}-${endIndex} 条,共 ${filteredItemsData.length} 条记录`;
|
||||
}
|
||||
|
||||
if (totalPagesElement) {
|
||||
totalPagesElement.textContent = totalItemsPages;
|
||||
}
|
||||
|
||||
if (pageInputElement) {
|
||||
pageInputElement.value = currentItemsPage;
|
||||
pageInputElement.max = totalItemsPages;
|
||||
}
|
||||
|
||||
// 更新分页按钮状态
|
||||
updateItemsPaginationButtons();
|
||||
}
|
||||
|
||||
// 更新分页按钮状态
|
||||
function updateItemsPaginationButtons() {
|
||||
const firstPageBtn = document.getElementById('itemsFirstPage');
|
||||
const prevPageBtn = document.getElementById('itemsPrevPage');
|
||||
const nextPageBtn = document.getElementById('itemsNextPage');
|
||||
const lastPageBtn = document.getElementById('itemsLastPage');
|
||||
|
||||
if (firstPageBtn) firstPageBtn.disabled = currentItemsPage <= 1;
|
||||
if (prevPageBtn) prevPageBtn.disabled = currentItemsPage <= 1;
|
||||
if (nextPageBtn) nextPageBtn.disabled = currentItemsPage >= totalItemsPages;
|
||||
if (lastPageBtn) lastPageBtn.disabled = currentItemsPage >= totalItemsPages;
|
||||
}
|
||||
|
||||
// 跳转到指定页面
|
||||
function goToItemsPage(page) {
|
||||
if (page < 1 || page > totalItemsPages) return;
|
||||
|
||||
currentItemsPage = page;
|
||||
displayCurrentPageItems();
|
||||
updateItemsPagination();
|
||||
}
|
||||
|
||||
// 处理页面输入框的回车事件
|
||||
function handleItemsPageInput(event) {
|
||||
if (event.key === 'Enter') {
|
||||
const pageInput = event.target;
|
||||
const page = parseInt(pageInput.value);
|
||||
|
||||
if (page >= 1 && page <= totalItemsPages) {
|
||||
goToItemsPage(page);
|
||||
} else {
|
||||
pageInput.value = currentItemsPage;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 改变每页显示数量
|
||||
function changeItemsPageSize() {
|
||||
const pageSizeSelect = document.getElementById('itemsPageSize');
|
||||
if (!pageSizeSelect) return;
|
||||
|
||||
itemsPerPage = parseInt(pageSizeSelect.value);
|
||||
|
||||
// 重新计算总页数
|
||||
totalItemsPages = Math.ceil(filteredItemsData.length / itemsPerPage);
|
||||
|
||||
// 调整当前页码,确保不超出范围
|
||||
if (currentItemsPage > totalItemsPages) {
|
||||
currentItemsPage = Math.max(1, totalItemsPages);
|
||||
}
|
||||
|
||||
// 重新显示数据
|
||||
displayCurrentPageItems();
|
||||
updateItemsPagination();
|
||||
}
|
||||
|
||||
// 初始化商品搜索功能
|
||||
function initItemsSearch() {
|
||||
// 初始化分页大小
|
||||
const pageSizeSelect = document.getElementById('itemsPageSize');
|
||||
if (pageSizeSelect) {
|
||||
itemsPerPage = parseInt(pageSizeSelect.value) || 20;
|
||||
pageSizeSelect.addEventListener('change', changeItemsPageSize);
|
||||
}
|
||||
|
||||
// 初始化搜索输入框事件监听器
|
||||
const searchInput = document.getElementById('itemSearchInput');
|
||||
if (searchInput) {
|
||||
// 使用防抖来避免频繁搜索
|
||||
let searchTimeout;
|
||||
searchInput.addEventListener('input', function() {
|
||||
clearTimeout(searchTimeout);
|
||||
searchTimeout = setTimeout(() => {
|
||||
filterItems();
|
||||
}, 300); // 300ms 防抖延迟
|
||||
});
|
||||
}
|
||||
|
||||
// 初始化页面输入框事件监听器
|
||||
const pageInput = document.getElementById('itemsPageInput');
|
||||
if (pageInput) {
|
||||
pageInput.addEventListener('keydown', handleItemsPageInput);
|
||||
}
|
||||
}
|
||||
|
||||
// 刷新商品列表
|
||||
async function refreshItems() {
|
||||
await refreshItemsData();
|
||||
|
Loading…
x
Reference in New Issue
Block a user