mirror of
https://github.com/zhinianboke/xianyu-auto-reply.git
synced 2025-08-30 01:27:35 +08:00
Compare commits
3 Commits
0b9b81bc4e
...
43351ead93
Author | SHA1 | Date | |
---|---|---|---|
![]() |
43351ead93 | ||
![]() |
20ff87f47b | ||
![]() |
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继续正常工作,新增字段可选
|
|
@ -1,4 +1,4 @@
|
|||||||
# 🐟 闲鱼自动回复系统
|
# 🐟 闲鱼自动回复系统 (另外提供cursor pro 14天试用账号,物美价廉,欢迎联系)
|
||||||
|
|
||||||
[](https://github.com/zhinianboke/xianyu-auto-reply)
|
[](https://github.com/zhinianboke/xianyu-auto-reply)
|
||||||
[](https://github.com/zhinianboke/xianyu-auto-reply#-快速开始)
|
[](https://github.com/zhinianboke/xianyu-auto-reply#-快速开始)
|
||||||
|
@ -3658,9 +3658,10 @@ def get_table_data(table_name: str, admin_user: Dict[str, Any] = Depends(require
|
|||||||
|
|
||||||
# 验证表名安全性
|
# 验证表名安全性
|
||||||
allowed_tables = [
|
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',
|
'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:
|
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 = [
|
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',
|
'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:
|
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 = [
|
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',
|
'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="">请选择数据表...</option>
|
||||||
<option value="users">users - 用户表</option>
|
<option value="users">users - 用户表</option>
|
||||||
<option value="cookies">cookies - Cookie账号表</option>
|
<option value="cookies">cookies - Cookie账号表</option>
|
||||||
|
<option value="cookie_status">cookie_status - Cookie状态表</option>
|
||||||
<option value="keywords">keywords - 关键字表</option>
|
<option value="keywords">keywords - 关键字表</option>
|
||||||
<option value="default_replies">default_replies - 默认回复表</option>
|
<option value="default_replies">default_replies - 默认回复表</option>
|
||||||
|
<option value="default_reply_records">default_reply_records - 默认回复记录表</option>
|
||||||
<option value="ai_reply_settings">ai_reply_settings - AI回复设置表</option>
|
<option value="ai_reply_settings">ai_reply_settings - AI回复设置表</option>
|
||||||
|
<option value="ai_conversations">ai_conversations - AI对话历史表</option>
|
||||||
|
<option value="ai_item_cache">ai_item_cache - AI商品信息缓存表</option>
|
||||||
|
<option value="item_info">item_info - 商品信息表</option>
|
||||||
<option value="message_notifications">message_notifications - 消息通知表</option>
|
<option value="message_notifications">message_notifications - 消息通知表</option>
|
||||||
<option value="cards">cards - 卡券表</option>
|
<option value="cards">cards - 卡券表</option>
|
||||||
<option value="delivery_rules">delivery_rules - 发货规则表</option>
|
<option value="delivery_rules">delivery_rules - 发货规则表</option>
|
||||||
<option value="notification_channels">notification_channels - 通知渠道表</option>
|
<option value="notification_channels">notification_channels - 通知渠道表</option>
|
||||||
<option value="user_settings">user_settings - 用户设置表</option>
|
<option value="user_settings">user_settings - 用户设置表</option>
|
||||||
|
<option value="system_settings">system_settings - 系统设置表</option>
|
||||||
<option value="email_verifications">email_verifications - 邮箱验证表</option>
|
<option value="email_verifications">email_verifications - 邮箱验证表</option>
|
||||||
<option value="captcha_codes">captcha_codes - 验证码表</option>
|
<option value="captcha_codes">captcha_codes - 验证码表</option>
|
||||||
</select>
|
</select>
|
||||||
@ -254,14 +260,20 @@
|
|||||||
const tableDescriptions = {
|
const tableDescriptions = {
|
||||||
'users': '用户表',
|
'users': '用户表',
|
||||||
'cookies': 'Cookie账号表',
|
'cookies': 'Cookie账号表',
|
||||||
|
'cookie_status': 'Cookie状态表',
|
||||||
'keywords': '关键字表',
|
'keywords': '关键字表',
|
||||||
'default_replies': '默认回复表',
|
'default_replies': '默认回复表',
|
||||||
|
'default_reply_records': '默认回复记录表',
|
||||||
'ai_reply_settings': 'AI回复设置表',
|
'ai_reply_settings': 'AI回复设置表',
|
||||||
|
'ai_conversations': 'AI对话历史表',
|
||||||
|
'ai_item_cache': 'AI商品信息缓存表',
|
||||||
|
'item_info': '商品信息表',
|
||||||
'message_notifications': '消息通知表',
|
'message_notifications': '消息通知表',
|
||||||
'cards': '卡券表',
|
'cards': '卡券表',
|
||||||
'delivery_rules': '发货规则表',
|
'delivery_rules': '发货规则表',
|
||||||
'notification_channels': '通知渠道表',
|
'notification_channels': '通知渠道表',
|
||||||
'user_settings': '用户设置表',
|
'user_settings': '用户设置表',
|
||||||
|
'system_settings': '系统设置表',
|
||||||
'email_verifications': '邮箱验证表',
|
'email_verifications': '邮箱验证表',
|
||||||
'captcha_codes': '验证码表'
|
'captcha_codes': '验证码表'
|
||||||
};
|
};
|
||||||
|
@ -314,16 +314,17 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="content-body">
|
<div class="content-body">
|
||||||
<!-- Cookie筛选 -->
|
<!-- 筛选和搜索 -->
|
||||||
<div class="card mb-4">
|
<div class="card mb-4">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="row align-items-end">
|
<div class="row align-items-end mb-3">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label for="itemCookieFilter" class="form-label">筛选账号</label>
|
<label for="itemCookieFilter" class="form-label">筛选账号</label>
|
||||||
<select class="form-select" id="itemCookieFilter" onchange="loadItemsByCookie()">
|
<select class="form-select" id="itemCookieFilter" onchange="loadItemsByCookie()">
|
||||||
<option value="">所有账号</option>
|
<option value="">所有账号</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="d-flex justify-content-end align-items-end gap-2">
|
<div class="d-flex justify-content-end align-items-end gap-2">
|
||||||
<!-- 页码输入 -->
|
<!-- 页码输入 -->
|
||||||
@ -345,18 +346,33 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
|
|
||||||
<!-- 商品列表 -->
|
<!-- 商品列表 -->
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header d-flex justify-content-between align-items-center">
|
<div class="card-header d-flex justify-content-between align-items-center">
|
||||||
<h5 class="mb-0">商品列表(自动发货根据商品标题和商品详情匹配关键字)</h5>
|
<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>
|
<button class="btn btn-sm btn-outline-danger" onclick="batchDeleteItems()" id="batchDeleteBtn" disabled>
|
||||||
<i class="bi bi-trash"></i> 批量删除
|
<i class="bi bi-trash"></i> 批量删除
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<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">
|
<div class="table-responsive">
|
||||||
<table class="table table-hover">
|
<table class="table table-hover">
|
||||||
<thead>
|
<thead>
|
||||||
@ -375,12 +391,54 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody id="itemsTableBody">
|
<tbody id="itemsTableBody">
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="7" class="text-center text-muted">加载中...</td>
|
<td colspan="8" class="text-center text-muted">加载中...</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
342
static/js/app.js
342
static/js/app.js
@ -17,6 +17,14 @@ let accountKeywordCache = {};
|
|||||||
let cacheTimestamp = 0;
|
let cacheTimestamp = 0;
|
||||||
const CACHE_DURATION = 30000; // 30秒缓存
|
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();
|
initTooltips();
|
||||||
|
|
||||||
|
// 初始化商品搜索功能
|
||||||
|
initItemsSearch();
|
||||||
|
|
||||||
// 点击侧边栏外部关闭移动端菜单
|
// 点击侧边栏外部关闭移动端菜单
|
||||||
document.addEventListener('click', function(e) {
|
document.addEventListener('click', function(e) {
|
||||||
const sidebar = document.getElementById('sidebar');
|
const sidebar = document.getElementById('sidebar');
|
||||||
@ -4977,94 +4988,291 @@ async function loadItemsByCookie() {
|
|||||||
|
|
||||||
// 显示商品列表
|
// 显示商品列表
|
||||||
function displayItems(items) {
|
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');
|
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>';
|
tbody.innerHTML = '<tr><td colspan="8" class="text-center text-muted">暂无商品数据</td></tr>';
|
||||||
// 重置选择状态
|
resetItemsSelection();
|
||||||
const selectAllCheckbox = document.getElementById('selectAllItems');
|
return;
|
||||||
if (selectAllCheckbox) {
|
|
||||||
selectAllCheckbox.checked = false;
|
|
||||||
selectAllCheckbox.indeterminate = false;
|
|
||||||
}
|
|
||||||
updateBatchDeleteButton();
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const itemsHtml = items.map(item => {
|
// 计算当前页的数据范围
|
||||||
// 处理商品标题显示
|
const startIndex = (currentItemsPage - 1) * itemsPerPage;
|
||||||
let itemTitleDisplay = item.item_title || '未设置';
|
const endIndex = startIndex + itemsPerPage;
|
||||||
if (itemTitleDisplay.length > 30) {
|
const currentPageItems = filteredItemsData.slice(startIndex, endIndex);
|
||||||
itemTitleDisplay = itemTitleDisplay.substring(0, 30) + '...';
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理商品详情显示
|
const itemsHtml = currentPageItems.map(item => {
|
||||||
let itemDetailDisplay = '未设置';
|
// 处理商品标题显示
|
||||||
if (item.item_detail) {
|
let itemTitleDisplay = item.item_title || '未设置';
|
||||||
try {
|
if (itemTitleDisplay.length > 30) {
|
||||||
// 尝试解析JSON并提取有用信息
|
itemTitleDisplay = itemTitleDisplay.substring(0, 30) + '...';
|
||||||
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 ? '...' : '');
|
let itemDetailDisplay = '未设置';
|
||||||
|
if (item.item_detail) {
|
||||||
|
const detailText = getItemDetailText(item.item_detail);
|
||||||
|
itemDetailDisplay = detailText.substring(0, 50) + (detailText.length > 50 ? '...' : '');
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// 多规格状态显示
|
// 多规格状态显示
|
||||||
const isMultiSpec = item.is_multi_spec;
|
const isMultiSpec = item.is_multi_spec;
|
||||||
const multiSpecDisplay = isMultiSpec ?
|
const multiSpecDisplay = isMultiSpec ?
|
||||||
'<span class="badge bg-success">多规格</span>' :
|
'<span class="badge bg-success">多规格</span>' :
|
||||||
'<span class="badge bg-secondary">普通</span>';
|
'<span class="badge bg-secondary">普通</span>';
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<input type="checkbox" name="itemCheckbox"
|
<input type="checkbox" name="itemCheckbox"
|
||||||
data-cookie-id="${escapeHtml(item.cookie_id)}"
|
data-cookie-id="${escapeHtml(item.cookie_id)}"
|
||||||
data-item-id="${escapeHtml(item.item_id)}"
|
data-item-id="${escapeHtml(item.item_id)}"
|
||||||
onchange="updateSelectAllState()">
|
onchange="updateSelectAllState()">
|
||||||
</td>
|
</td>
|
||||||
<td>${escapeHtml(item.cookie_id)}</td>
|
<td>${escapeHtml(item.cookie_id)}</td>
|
||||||
<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(getItemDetailText(item.item_detail || ''))}">${escapeHtml(itemDetailDisplay)}</td>
|
||||||
<td>${multiSpecDisplay}</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">
|
||||||
<button class="btn btn-sm btn-outline-primary" onclick="editItem('${escapeHtml(item.cookie_id)}', '${escapeHtml(item.item_id)}')" title="编辑详情">
|
<button class="btn btn-sm btn-outline-primary" onclick="editItem('${escapeHtml(item.cookie_id)}', '${escapeHtml(item.item_id)}')" title="编辑详情">
|
||||||
<i class="bi bi-pencil"></i>
|
<i class="bi bi-pencil"></i>
|
||||||
</button>
|
</button>
|
||||||
<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 ? '关闭多规格' : '开启多规格'}">
|
<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>
|
<i class="bi ${isMultiSpec ? 'bi-toggle-on' : 'bi-toggle-off'}"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
`;
|
`;
|
||||||
}).join('');
|
}).join('');
|
||||||
|
|
||||||
// 更新表格内容
|
// 更新表格内容
|
||||||
tbody.innerHTML = itemsHtml;
|
tbody.innerHTML = itemsHtml;
|
||||||
|
|
||||||
// 重置选择状态
|
// 重置选择状态
|
||||||
|
resetItemsSelection();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置商品选择状态
|
||||||
|
function resetItemsSelection() {
|
||||||
const selectAllCheckbox = document.getElementById('selectAllItems');
|
const selectAllCheckbox = document.getElementById('selectAllItems');
|
||||||
if (selectAllCheckbox) {
|
if (selectAllCheckbox) {
|
||||||
selectAllCheckbox.checked = false;
|
selectAllCheckbox.checked = false;
|
||||||
selectAllCheckbox.indeterminate = false;
|
selectAllCheckbox.indeterminate = false;
|
||||||
}
|
}
|
||||||
updateBatchDeleteButton();
|
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() {
|
async function refreshItems() {
|
||||||
await refreshItemsData();
|
await refreshItemsData();
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 170 KiB After Width: | Height: | Size: 166 KiB |
Loading…
x
Reference in New Issue
Block a user