mirror of
https://github.com/Cp0204/quark-auto-save.git
synced 2026-01-19 11:39:37 +08:00
Compare commits
15 Commits
0e458e934e
...
2b7c30b964
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2b7c30b964 | ||
|
|
bdd6643c43 | ||
|
|
223a46d906 | ||
|
|
84a19e5f0e | ||
|
|
ffe6bfdbaa | ||
|
|
0f121a2a4a | ||
|
|
85cd4d3a9c | ||
|
|
ecf4a26a53 | ||
|
|
732b184ab4 | ||
|
|
9cd952799d | ||
|
|
09d99a052d | ||
|
|
2e90f9fbac | ||
|
|
544d24b145 | ||
|
|
8933311072 | ||
|
|
28d68f1f71 |
@ -11,7 +11,7 @@
|
||||
- **WebUI**:对整个 WebUI 进行了重塑,增加了更多实用功能,如文件选择和预览界面的排序功能、资源搜索的过滤功能、TMDB 和豆瓣搜索功能、页面视图切换功能、账号设置功能等等。
|
||||
- **查重逻辑**:支持优先通过历史转存记录查重,对于有转存记录的文件,即使删除网盘文件,也不会重复转存。
|
||||
- **Aria2**:支持成功添加 Aria2 下载任务后自动删除夸克网盘内对应的文件,清理网盘空间。
|
||||
- **文件整理**:支持浏览和管理多个夸克账号的网盘文件,支持批量重命名(支持应用完整的命名、过滤规则和撤销重命名等操作)、删除文件等操作。
|
||||
- **文件整理**:支持浏览和管理多个夸克账号的网盘文件,支持单项/批量重命名(支持应用完整的命名、过滤规则和撤销重命名等操作)、移动文件、删除文件、新建文件夹等操作。
|
||||
|
||||
本项目修改后的版本为个人需求定制版,目的是满足我自己的使用需求,某些(我不用的)功能可能会因为修改而出现 BUG,不一定会被修复。若你要使用本项目,请知晓本人不是程序员,我无法保证本项目的稳定性,如果你在使用过程中发现了 BUG,可以在 Issues 中提交,但不保证每个 BUG 都能被修复,请谨慎使用,风险自担。
|
||||
|
||||
|
||||
127
app/run.py
127
app/run.py
@ -40,6 +40,14 @@ from quark_auto_save import Config, format_bytes
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
from quark_auto_save import extract_episode_number, sort_file_by_name, chinese_to_arabic, is_date_format
|
||||
|
||||
# 导入拼音排序工具
|
||||
try:
|
||||
from utils.pinyin_sort import get_filename_pinyin_sort_key
|
||||
except ImportError:
|
||||
# 如果导入失败,使用简单的小写排序作为备用
|
||||
def get_filename_pinyin_sort_key(filename):
|
||||
return filename.lower()
|
||||
|
||||
# 导入数据库模块
|
||||
try:
|
||||
# 先尝试相对导入
|
||||
@ -859,6 +867,8 @@ def get_share_detail():
|
||||
|
||||
if episode_num is not None:
|
||||
file["file_name_re"] = episode_pattern.replace("[]", f"{episode_num:02d}") + extension
|
||||
# 添加episode_number字段用于前端排序
|
||||
file["episode_number"] = episode_num
|
||||
else:
|
||||
# 没有提取到集号,显示无法识别的提示
|
||||
file["file_name_re"] = "× 无法识别剧集编号"
|
||||
@ -1011,6 +1021,114 @@ def delete_file():
|
||||
return jsonify(response)
|
||||
|
||||
|
||||
@app.route("/move_file", methods=["POST"])
|
||||
def move_file():
|
||||
if not is_login():
|
||||
return jsonify({"success": False, "message": "未登录"})
|
||||
|
||||
# 获取账号索引参数
|
||||
account_index = int(request.json.get("account_index", 0))
|
||||
|
||||
# 验证账号索引
|
||||
if account_index < 0 or account_index >= len(config_data["cookie"]):
|
||||
return jsonify({"success": False, "message": "账号索引无效"})
|
||||
|
||||
account = Quark(config_data["cookie"][account_index], account_index)
|
||||
|
||||
# 获取参数
|
||||
file_ids = request.json.get("file_ids", [])
|
||||
target_folder_id = request.json.get("target_folder_id")
|
||||
|
||||
if not file_ids:
|
||||
return jsonify({"success": False, "message": "缺少文件ID列表"})
|
||||
|
||||
if not target_folder_id:
|
||||
return jsonify({"success": False, "message": "缺少目标文件夹ID"})
|
||||
|
||||
try:
|
||||
# 调用夸克网盘的移动API
|
||||
response = account.move(file_ids, target_folder_id)
|
||||
|
||||
if response["code"] == 0:
|
||||
return jsonify({
|
||||
"success": True,
|
||||
"message": f"成功移动 {len(file_ids)} 个文件",
|
||||
"moved_count": len(file_ids)
|
||||
})
|
||||
else:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"message": response.get("message", "移动失败")
|
||||
})
|
||||
except Exception as e:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"message": f"移动文件时出错: {str(e)}"
|
||||
})
|
||||
|
||||
|
||||
@app.route("/create_folder", methods=["POST"])
|
||||
def create_folder():
|
||||
if not is_login():
|
||||
return jsonify({"success": False, "message": "未登录"})
|
||||
|
||||
# 获取请求参数
|
||||
data = request.json
|
||||
parent_folder_id = data.get("parent_folder_id")
|
||||
folder_name = data.get("folder_name", "新建文件夹")
|
||||
account_index = int(data.get("account_index", 0))
|
||||
|
||||
# 验证参数
|
||||
if not parent_folder_id:
|
||||
return jsonify({"success": False, "message": "缺少父目录ID"})
|
||||
|
||||
# 验证账号索引
|
||||
if account_index < 0 or account_index >= len(config_data["cookie"]):
|
||||
return jsonify({"success": False, "message": "账号索引无效"})
|
||||
|
||||
try:
|
||||
# 初始化夸克网盘客户端
|
||||
account = Quark(config_data["cookie"][account_index], account_index)
|
||||
|
||||
# 调用新建文件夹API
|
||||
response = account.mkdir_in_folder(parent_folder_id, folder_name)
|
||||
|
||||
if response.get("code") == 0:
|
||||
# 创建成功,返回新文件夹信息
|
||||
new_folder = response.get("data", {})
|
||||
return jsonify({
|
||||
"success": True,
|
||||
"message": "文件夹创建成功",
|
||||
"data": {
|
||||
"fid": new_folder.get("fid"),
|
||||
"file_name": new_folder.get("file_name", folder_name),
|
||||
"dir": True,
|
||||
"size": 0,
|
||||
"updated_at": new_folder.get("updated_at"),
|
||||
"include_items": 0
|
||||
}
|
||||
})
|
||||
else:
|
||||
# 处理特定的错误信息
|
||||
error_message = response.get("message", "创建文件夹失败")
|
||||
|
||||
# 检查是否是同名文件夹冲突
|
||||
if "同名" in error_message or "已存在" in error_message or "重复" in error_message or "doloading" in error_message:
|
||||
error_message = "已存在同名文件夹,请修改名称后再试"
|
||||
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"message": error_message
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f">>> 创建文件夹时出错: {str(e)}")
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"message": f"创建文件夹时出错: {str(e)}"
|
||||
})
|
||||
|
||||
|
||||
# 添加任务接口
|
||||
@app.route("/api/add_task", methods=["POST"])
|
||||
def add_task():
|
||||
@ -1581,9 +1699,11 @@ def get_file_list():
|
||||
# 优化排序:使用更高效的排序方法
|
||||
def get_sort_key(file_item):
|
||||
if sort_by == "file_name":
|
||||
return file_item["file_name"].lower()
|
||||
# 使用拼音排序
|
||||
return get_filename_pinyin_sort_key(file_item["file_name"])
|
||||
elif sort_by == "file_size":
|
||||
return file_item["size"] if not file_item["dir"] else 0
|
||||
# 文件夹按项目数量排序,文件按大小排序
|
||||
return file_item.get("include_items", 0) if file_item["dir"] else file_item["size"]
|
||||
else: # updated_at
|
||||
return file_item["updated_at"]
|
||||
|
||||
@ -1738,7 +1858,8 @@ def preview_rename():
|
||||
preview_results.append({
|
||||
"original_name": file["file_name"],
|
||||
"new_name": new_name,
|
||||
"file_id": file["fid"]
|
||||
"file_id": file["fid"],
|
||||
"episode_number": episode_num # 添加集数字段用于前端排序
|
||||
})
|
||||
else:
|
||||
# 没有提取到集号,显示无法识别的提示
|
||||
|
||||
@ -174,6 +174,31 @@ class RecordDB:
|
||||
total_records = cursor.fetchone()[0]
|
||||
|
||||
# 获取分页数据
|
||||
if sort_by in ["task_name", "original_name", "renamed_to"]:
|
||||
# 对于需要拼音排序的字段,先获取所有数据然后在Python中进行拼音排序
|
||||
query_sql = f"SELECT * FROM transfer_records {where_sql}"
|
||||
cursor.execute(query_sql, params)
|
||||
all_records = cursor.fetchall()
|
||||
|
||||
# 使用拼音排序
|
||||
from utils.pinyin_sort import get_filename_pinyin_sort_key
|
||||
|
||||
# 根据排序字段选择对应的索引
|
||||
field_index_map = {
|
||||
"task_name": 2, # task_name字段索引
|
||||
"original_name": 3, # original_name字段索引
|
||||
"renamed_to": 4 # renamed_to字段索引
|
||||
}
|
||||
field_index = field_index_map[sort_by]
|
||||
|
||||
sorted_records = sorted(all_records, key=lambda x: get_filename_pinyin_sort_key(x[field_index]), reverse=(order_direction == "DESC"))
|
||||
|
||||
# 手动分页
|
||||
start_idx = offset
|
||||
end_idx = offset + page_size
|
||||
records = sorted_records[start_idx:end_idx]
|
||||
else:
|
||||
# 其他字段使用SQL排序
|
||||
query_sql = f"SELECT * FROM transfer_records {where_sql} ORDER BY {sort_by} {order_direction} LIMIT ? OFFSET ?"
|
||||
cursor.execute(query_sql, params + [page_size, offset])
|
||||
records = cursor.fetchall()
|
||||
|
||||
@ -804,12 +804,16 @@ main div[v-if="activeTab === 'config'"] .row.title:first-child {
|
||||
/* 文件夹图标样式 */
|
||||
.bi-folder {
|
||||
color: var(--dark-text-color);
|
||||
font-size: 0.98rem;
|
||||
font-size: 1.01rem;
|
||||
position: relative;
|
||||
top: 0.5px;
|
||||
}
|
||||
|
||||
/* 重置文件夹图标样式 */
|
||||
.bi-folder-x {
|
||||
font-size: 0.98rem;
|
||||
font-size: 1.01rem;
|
||||
position: relative;
|
||||
top: 0.5px;
|
||||
}
|
||||
|
||||
/* 恢复图标样式 */
|
||||
@ -876,7 +880,9 @@ select.form-control {
|
||||
/* 链接图标样式 */
|
||||
.bi-link-45deg {
|
||||
color: var(--dark-text-color);
|
||||
font-size: 1.15rem;
|
||||
font-size: 1.2rem;
|
||||
position: relative;
|
||||
top: 0.5px;
|
||||
}
|
||||
|
||||
/* 谷歌图标样式 */
|
||||
@ -2118,7 +2124,7 @@ div.jsoneditor-tree button.jsoneditor-button:focus {
|
||||
|
||||
/* 侧边栏菜单项图标样式 */
|
||||
.sidebar .nav-link .bi-list-ul {
|
||||
font-size: 1.08rem;
|
||||
font-size: 1.09rem;
|
||||
position: relative;
|
||||
top: 0.5px; /* 向下微调 */
|
||||
}
|
||||
@ -4187,6 +4193,11 @@ select.task-filter-select,
|
||||
color: var(--focus-border-color);
|
||||
}
|
||||
|
||||
/* 文件整理页面面包屑导航当前目录样式 */
|
||||
.file-manager-breadcrumb .breadcrumb-item .text-muted {
|
||||
color: var(--dark-text-color) !important;
|
||||
}
|
||||
|
||||
/* 隐藏Bootstrap默认的面包屑分隔符 */
|
||||
.file-manager-breadcrumb .breadcrumb-item + .breadcrumb-item::before {
|
||||
color: var(--dark-text-color);
|
||||
@ -4244,6 +4255,13 @@ select.task-filter-select,
|
||||
font-size: 1.17rem;
|
||||
}
|
||||
|
||||
/* 文件整理页面新建文件夹按钮图标大小 */
|
||||
.batch-rename-btn .bi-folder-plus {
|
||||
font-size: 1.01rem;
|
||||
position: relative;
|
||||
top: 0px;
|
||||
}
|
||||
|
||||
/* 确保文件整理页面的Plex和AList按钮样式与任务列表一致 */
|
||||
.batch-rename-btn.btn-outline-plex,
|
||||
.batch-rename-btn.btn-outline-alist {
|
||||
@ -4288,6 +4306,8 @@ select.task-filter-select,
|
||||
filter: brightness(0) invert(1);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* 文件表格中的展开按钮 */
|
||||
.expand-button {
|
||||
position: absolute;
|
||||
@ -4582,6 +4602,11 @@ table.selectable-files .rename-record-btn:hover {
|
||||
color: #0A42CC !important;
|
||||
}
|
||||
|
||||
/* 文件整理页面移动按钮悬停效果 */
|
||||
table.selectable-files .move-record-btn:hover {
|
||||
color: #0A42CC !important;
|
||||
}
|
||||
|
||||
/* 文件整理页面删除按钮悬停效果 */
|
||||
table.selectable-files .delete-record-btn:hover {
|
||||
color: #b02a37 !important;
|
||||
@ -4694,17 +4719,6 @@ table.selectable-records tr td {
|
||||
|
||||
|
||||
|
||||
/* 文件大小列中展开行的删除按钮特殊处理 */
|
||||
tr:has(.expanded-text) .file-size-cell .delete-record-btn,
|
||||
.selectable-records tbody tr:has(.expanded-text) .file-size-cell .delete-record-btn,
|
||||
#fileSelectModal .table tr:has([style*="white-space: normal"]) .file-size-cell .delete-record-btn,
|
||||
.selectable-files tr:has([style*="white-space: normal"]) .file-size-cell .delete-record-btn {
|
||||
top: 8px !important; /* 强制固定位置 */
|
||||
transform: none !important; /* 确保不使用任何转换 */
|
||||
left: 32px !important; /* 确保左边距固定,在重命名按钮右侧 */
|
||||
/* 不设置display,保持默认的隐藏状态 */
|
||||
}
|
||||
|
||||
/* 文件大小列中展开行的重命名按钮特殊处理 */
|
||||
tr:has(.expanded-text) .file-size-cell .rename-record-btn,
|
||||
.selectable-records tbody tr:has(.expanded-text) .file-size-cell .rename-record-btn,
|
||||
@ -4716,6 +4730,28 @@ tr:has(.expanded-text) .file-size-cell .rename-record-btn,
|
||||
/* 不设置display,保持默认的隐藏状态 */
|
||||
}
|
||||
|
||||
/* 文件大小列中展开行的移动按钮特殊处理 */
|
||||
tr:has(.expanded-text) .file-size-cell .move-record-btn,
|
||||
.selectable-records tbody tr:has(.expanded-text) .file-size-cell .move-record-btn,
|
||||
#fileSelectModal .table tr:has([style*="white-space: normal"]) .file-size-cell .move-record-btn,
|
||||
.selectable-files tr:has([style*="white-space: normal"]) .file-size-cell .move-record-btn {
|
||||
top: 8px !important; /* 强制固定位置 */
|
||||
transform: none !important; /* 确保不使用任何转换 */
|
||||
left: 32px !important; /* 确保左边距固定,在重命名按钮右侧 */
|
||||
/* 不设置display,保持默认的隐藏状态 */
|
||||
}
|
||||
|
||||
/* 文件大小列中展开行的删除按钮特殊处理 */
|
||||
tr:has(.expanded-text) .file-size-cell .delete-record-btn,
|
||||
.selectable-records tbody tr:has(.expanded-text) .file-size-cell .delete-record-btn,
|
||||
#fileSelectModal .table tr:has([style*="white-space: normal"]) .file-size-cell .delete-record-btn,
|
||||
.selectable-files tr:has([style*="white-space: normal"]) .file-size-cell .delete-record-btn {
|
||||
top: 8px !important; /* 强制固定位置 */
|
||||
transform: none !important; /* 确保不使用任何转换 */
|
||||
left: 59px !important; /* 确保左边距固定,在移动按钮右侧 */
|
||||
/* 不设置display,保持默认的隐藏状态 */
|
||||
}
|
||||
|
||||
/* 修复删除按钮位置问题 - 使用更强制的方法 */
|
||||
.delete-record-btn {
|
||||
color: #dc3545;
|
||||
@ -4803,11 +4839,34 @@ tr.selected-record .file-size-cell .delete-record-btn,
|
||||
|
||||
/* 文件整理页面的重命名文件按钮图标大小 */
|
||||
.selectable-files .rename-record-btn .bi-pencil {
|
||||
font-size: 0.98rem;
|
||||
font-size: 0.99rem;
|
||||
position: relative;
|
||||
left: 0.5px;
|
||||
}
|
||||
|
||||
/* 移动按钮样式 */
|
||||
.selectable-files .move-record-btn {
|
||||
color: var(--focus-border-color);
|
||||
cursor: pointer;
|
||||
display: none;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 4px;
|
||||
position: absolute;
|
||||
left: 32px; /* 在重命名按钮右侧,调整间距 */
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 文件整理页面的移动文件按钮图标大小 */
|
||||
.selectable-files .move-record-btn .bi-arrow-up-right-circle {
|
||||
font-size: 1.07rem;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* 删除按钮样式调整 */
|
||||
.selectable-files .delete-record-btn {
|
||||
color: #dc3545;
|
||||
@ -4820,7 +4879,7 @@ tr.selected-record .file-size-cell .delete-record-btn,
|
||||
border-radius: 4px;
|
||||
position: absolute;
|
||||
right: auto; /* 移除右对齐 */
|
||||
left: 32px; /* 在重命名按钮右侧,调整间距 */
|
||||
left: 59px; /* 在移动按钮右侧,调整间距 */
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
font-size: 14px;
|
||||
@ -4832,9 +4891,11 @@ tr.selected-record .file-size-cell .delete-record-btn,
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
/* 修复:确保在悬停和选中状态下重命名和删除按钮显示 */
|
||||
/* 修复:确保在悬停和选中状态下重命名、移动和删除按钮显示 */
|
||||
.selectable-files tr:hover .file-size-cell .rename-record-btn,
|
||||
.selectable-files tr.selected-file .file-size-cell .rename-record-btn,
|
||||
.selectable-files tr:hover .file-size-cell .move-record-btn,
|
||||
.selectable-files tr.selected-file .file-size-cell .move-record-btn,
|
||||
.selectable-files tr:hover .file-size-cell .delete-record-btn,
|
||||
.selectable-files tr.selected-file .file-size-cell .delete-record-btn {
|
||||
display: inline-flex !important;
|
||||
@ -4887,6 +4948,23 @@ body .selectable-files tr.selected-file .file-size-cell .rename-record-btn {
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
/* 确保文件整理页面的移动按钮在悬停和选中状态下始终可见 - 最高优先级 */
|
||||
body .selectable-files tbody tr:hover .file-size-cell .move-record-btn,
|
||||
body .selectable-files tr.selected-file .file-size-cell .move-record-btn {
|
||||
display: inline-flex !important;
|
||||
visibility: visible !important;
|
||||
position: absolute !important;
|
||||
top: 50% !important;
|
||||
transform: translateY(-50%) !important;
|
||||
left: 32px !important;
|
||||
width: 24px !important;
|
||||
height: 24px !important;
|
||||
align-items: center !important;
|
||||
justify-content: center !important;
|
||||
z-index: 5 !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
/* 确保文件整理页面的删除按钮在悬停和选中状态下始终可见 - 最高优先级 */
|
||||
body .selectable-files tbody tr:hover .file-size-cell .delete-record-btn,
|
||||
body .selectable-files tr.selected-file .file-size-cell .delete-record-btn {
|
||||
@ -4895,7 +4973,7 @@ body .selectable-files tr.selected-file .file-size-cell .delete-record-btn {
|
||||
position: absolute !important;
|
||||
top: 50% !important;
|
||||
transform: translateY(-50%) !important;
|
||||
left: 32px !important;
|
||||
left: 59px !important;
|
||||
width: 24px !important;
|
||||
height: 24px !important;
|
||||
align-items: center !important;
|
||||
@ -4921,6 +4999,23 @@ body .selectable-files tr.selected-file:has([style*="white-space: normal"]) .fil
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
/* 展开状态下的移动按钮悬停和选中状态 - 最高优先级 */
|
||||
body .selectable-files tbody tr:hover:has([style*="white-space: normal"]) .file-size-cell .move-record-btn,
|
||||
body .selectable-files tr.selected-file:has([style*="white-space: normal"]) .file-size-cell .move-record-btn {
|
||||
display: inline-flex !important;
|
||||
visibility: visible !important;
|
||||
position: absolute !important;
|
||||
top: 8px !important;
|
||||
transform: none !important;
|
||||
left: 32px !important;
|
||||
width: 24px !important;
|
||||
height: 24px !important;
|
||||
align-items: center !important;
|
||||
justify-content: center !important;
|
||||
z-index: 5 !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
/* 展开状态下的删除按钮悬停和选中状态 - 最高优先级 */
|
||||
body .selectable-files tbody tr:hover:has([style*="white-space: normal"]) .file-size-cell .delete-record-btn,
|
||||
body .selectable-files tr.selected-file:has([style*="white-space: normal"]) .file-size-cell .delete-record-btn {
|
||||
@ -4929,7 +5024,7 @@ body .selectable-files tr.selected-file:has([style*="white-space: normal"]) .fil
|
||||
position: absolute !important;
|
||||
top: 8px !important;
|
||||
transform: none !important;
|
||||
left: 32px !important;
|
||||
left: 59px !important;
|
||||
width: 24px !important;
|
||||
height: 24px !important;
|
||||
align-items: center !important;
|
||||
@ -5192,6 +5287,18 @@ body .selectable-files tr.selected-file:has([style*="white-space: normal"]) .fil
|
||||
color: var(--dark-text-color) !important;
|
||||
}
|
||||
|
||||
/* 移动文件模态框的取消按钮样式 */
|
||||
#fileSelectModal[data-modal-type="move"] .modal-footer .btn-cancel {
|
||||
background-color: var(--button-gray-background-color) !important;
|
||||
border-color: var(--button-gray-background-color) !important;
|
||||
color: var(--dark-text-color) !important;
|
||||
}
|
||||
#fileSelectModal[data-modal-type="move"] .modal-footer .btn-cancel:hover {
|
||||
background-color: #e0e2e6 !important;
|
||||
border-color: #e0e2e6 !important;
|
||||
color: var(--dark-text-color) !important;
|
||||
}
|
||||
|
||||
@media (max-width: 767.98px) {
|
||||
.file-manager-rule-bar-responsive {
|
||||
display: flex;
|
||||
|
||||
1
app/static/js/pinyin-pro.min.js
vendored
Executable file
1
app/static/js/pinyin-pro.min.js
vendored
Executable file
File diff suppressed because one or more lines are too long
@ -30,6 +30,19 @@ function sortFileByName(file) {
|
||||
let file_name_without_ext = filename.replace(/\.[^/.]+$/, '');
|
||||
let date_value = Infinity, episode_value = Infinity, segment_value = 0;
|
||||
|
||||
// 生成拼音排序键(第五级排序)
|
||||
let pinyin_sort_key;
|
||||
try {
|
||||
// 尝试使用 pinyinPro 库进行拼音转换
|
||||
if (typeof pinyinPro !== 'undefined') {
|
||||
pinyin_sort_key = pinyinPro.pinyin(filename, { toneType: 'none', type: 'string' }).toLowerCase();
|
||||
} else {
|
||||
pinyin_sort_key = filename.toLowerCase();
|
||||
}
|
||||
} catch (e) {
|
||||
pinyin_sort_key = filename.toLowerCase();
|
||||
}
|
||||
|
||||
// 1. 日期提取
|
||||
let match;
|
||||
// YYYY-MM-DD
|
||||
@ -142,7 +155,7 @@ function sortFileByName(file) {
|
||||
else if (/[中][集期话部篇]?|[集期话部篇]中/.test(filename)) segment_value = 2;
|
||||
else if (/[下][集期话部篇]?|[集期话部篇]下/.test(filename)) segment_value = 3;
|
||||
|
||||
return [date_value, episode_value, segment_value, update_time];
|
||||
return [date_value, episode_value, segment_value, update_time, pinyin_sort_key];
|
||||
}
|
||||
|
||||
// 用法:
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
<script src="./static/js/axios.min.js"></script>
|
||||
<script src="./static/js/v-jsoneditor.min.js"></script>
|
||||
<script src="./static/js/sort_file_by_name.js"></script>
|
||||
<script src="./static/js/pinyin-pro.min.js"></script>
|
||||
<script>
|
||||
// 添加检测文本溢出的自定义指令
|
||||
Vue.directive('check-overflow', {
|
||||
@ -748,7 +749,7 @@
|
||||
<div class="input-group">
|
||||
<input type="text" name="shareurl[]" class="form-control" v-model="task.shareurl" placeholder="必填" @blur="changeShareurl(task)">
|
||||
<div class="input-group-append" v-if="task.shareurl">
|
||||
<button type="button" class="btn btn-outline-secondary" @click="fileSelect.selectDir=true;fileSelect.previewRegex=false;showShareSelect(index)" title="选择文件夹"><i class="bi bi-folder"></i></button>
|
||||
<button type="button" class="btn btn-outline-secondary" @click="fileSelect.selectDir=true;fileSelect.previewRegex=false;showShareSelect(index)" title="选择需转存的文件夹"><i class="bi bi-folder"></i></button>
|
||||
<div class="input-group-text">
|
||||
<a target="_blank" :href="task.shareurl"><i class="bi bi-link-45deg"></i></a>
|
||||
</div>
|
||||
@ -763,7 +764,7 @@
|
||||
<input type="text" name="savepath[]" class="form-control" v-model="task.savepath" placeholder="必填" @focus="focusTaskname(index, task)">
|
||||
<div class="input-group-append">
|
||||
<button class="btn btn-outline-secondary" type="button" v-if="smart_param.savepath && smart_param.index == index && task.savepath != smart_param.origin_savepath" @click="task.savepath = smart_param.origin_savepath" title="恢复保存路径"><i class="bi bi-reply"></i></button>
|
||||
<button class="btn btn-outline-secondary" type="button" @click="showSavepathSelect(index)" title="选择文件夹"><i class="bi bi-folder"></i></button>
|
||||
<button class="btn btn-outline-secondary" type="button" @click="showSavepathSelect(index)" title="选择保存到的文件夹"><i class="bi bi-folder"></i></button>
|
||||
<button type="button" class="btn btn-outline-secondary" @click="resetFolder(index)" title="重置文件夹:此操作将删除当前保存路径中的所有文件及相关转存记录,且不可恢复,请谨慎操作"><i class="bi bi-folder-x"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
@ -801,18 +802,18 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row" title="只转存修改日期大于选中文件的文件,请在符合筛选条件的文件中进行选择,在更换分享链接时非常有用">
|
||||
<div class="form-group row" title="只转存比选中文件更新的文件,请在符合筛选条件的文件中进行选择,在更换分享链接时非常有用">
|
||||
<label class="col-sm-2 col-form-label">起始文件</label>
|
||||
<div class="col-sm-10">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" placeholder="可选,只转存修改日期大于此文件的文件,请在符合筛选条件的文件中进行选择" name="startfid[]" v-model="task.startfid">
|
||||
<input type="text" class="form-control" placeholder="可选,只转存比此文件更新的文件,请在符合筛选条件的文件中进行选择" name="startfid[]" v-model="task.startfid">
|
||||
<div class="input-group-append" v-if="task.shareurl">
|
||||
<button class="btn btn-outline-secondary" type="button" @click="fileSelect.selectDir=false;fileSelect.previewRegex=false;showShareSelect(index)" title="选择文件"><i class="bi bi-folder"></i></button>
|
||||
<button class="btn btn-outline-secondary" type="button" @click="fileSelect.selectDir=false;fileSelect.previewRegex=false;showShareSelect(index)" title="选择起始文件"><i class="bi bi-folder"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row" title="匹配成功的文件夹的所有嵌套目录都会被更新,并且会应用与根目录相同的重命名和过滤规则。注意:原理是逐级索引,深层嵌套目录的场景下效率非常低,慎用.*">
|
||||
<div class="form-group row" title="匹配成功的文件夹的所有嵌套目录都会被更新,并且会应用与根目录相同的正则命名和过滤规则。注意:原理是逐级索引,深层嵌套目录的场景下效率非常低,慎用.*">
|
||||
<label class="col-sm-2 col-form-label">更新目录</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" name="update_subdir[]" class="form-control" v-model="task.update_subdir" placeholder="可选,输入需要更新的子目录的文件夹名称(或正则表达式),多个项目用竖线分隔,如:4K|1080P">
|
||||
@ -824,7 +825,7 @@
|
||||
<div class="input-group">
|
||||
<input type="date" name="enddate[]" class="form-control date-input-no-icon" v-model="task.enddate" placeholder="可选" :ref="'enddate_' + index">
|
||||
<div class="input-group-append">
|
||||
<button type="button" class="btn btn-outline-secondary" @click="openDatePicker(index)" title="选择日期">
|
||||
<button type="button" class="btn btn-outline-secondary" @click="openDatePicker(index)" title="选择截止日期">
|
||||
<i class="bi bi-calendar3"></i>
|
||||
</button>
|
||||
</div>
|
||||
@ -1051,6 +1052,9 @@
|
||||
</div>
|
||||
<button type="button" class="btn btn-outline-plex batch-rename-btn" v-if="formData.plugins && formData.plugins.plex && formData.plugins.plex.url && formData.plugins.plex.token && formData.plugins.plex.quark_root_path && formData.button_display.refresh_plex !== 'disabled'" @click="refreshFileManagerPlexLibrary" title="刷新Plex媒体库"><img src="./static/Plex.svg" class="plex-icon"></button>
|
||||
<button type="button" class="btn btn-outline-alist batch-rename-btn" v-if="formData.plugins && formData.plugins.alist && formData.plugins.alist.url && formData.plugins.alist.token && formData.plugins.alist.storage_id && formData.button_display.refresh_alist !== 'disabled'" @click="refreshFileManagerAlistDirectory" title="刷新AList目录"><img src="https://cdn.jsdelivr.net/gh/alist-org/logo@main/logo.svg" class="alist-icon"></button>
|
||||
<button type="button" class="btn btn-outline-primary batch-rename-btn" @click="createNewFolder" title="新建文件夹">
|
||||
<i class="bi bi-folder-plus"></i>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-primary batch-rename-btn" @click="refreshCurrentFolderCache" title="刷新当前目录缓存">
|
||||
<i class="bi bi-arrow-clockwise"></i>
|
||||
</button>
|
||||
@ -1068,6 +1072,9 @@
|
||||
</div>
|
||||
<button type="button" class="btn btn-outline-plex batch-rename-btn" v-if="formData.plugins && formData.plugins.plex && formData.plugins.plex.url && formData.plugins.plex.token && formData.plugins.plex.quark_root_path && formData.button_display.refresh_plex !== 'disabled'" @click="refreshFileManagerPlexLibrary" title="刷新Plex媒体库"><img src="./static/Plex.svg" class="plex-icon"></button>
|
||||
<button type="button" class="btn btn-outline-alist batch-rename-btn" v-if="formData.plugins && formData.plugins.alist && formData.plugins.alist.url && formData.plugins.alist.token && formData.plugins.alist.storage_id && formData.button_display.refresh_alist !== 'disabled'" @click="refreshFileManagerAlistDirectory" title="刷新AList目录"><img src="https://cdn.jsdelivr.net/gh/alist-org/logo@main/logo.svg" class="alist-icon"></button>
|
||||
<button type="button" class="btn btn-outline-primary batch-rename-btn" @click="createNewFolder" title="新建文件夹">
|
||||
<i class="bi bi-folder-plus"></i>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-primary batch-rename-btn" @click="refreshCurrentFolderCache" title="刷新当前目录缓存">
|
||||
<i class="bi bi-arrow-clockwise"></i>
|
||||
</button>
|
||||
@ -1206,6 +1213,9 @@
|
||||
<span class="rename-record-btn" @click.stop="startRenameFile(file)" title="重命名文件">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</span>
|
||||
<span class="move-record-btn" @click.stop="startMoveFile(file)" title="移动文件">
|
||||
<i class="bi bi-arrow-up-right-circle"></i>
|
||||
</span>
|
||||
<span class="delete-record-btn" @click.stop="deleteSelectedFilesForManager(file.fid, file.file_name, file.dir)" title="删除文件">
|
||||
<i class="bi bi-trash3"></i>
|
||||
</span>
|
||||
@ -1302,9 +1312,9 @@
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" style="font-weight: 600; font-family: inherit; letter-spacing: normal;">
|
||||
<span v-if="fileSelect.previewRegex && fileSelect.index >= 0" style="font-weight: 600; font-family: inherit; letter-spacing: normal;">{{ formData.tasklist[fileSelect.index].use_sequence_naming ? '顺序命名预览' : (formData.tasklist[fileSelect.index].use_episode_naming ? '剧集命名预览' : '正则命名预览') }}</span>
|
||||
<span v-if="fileSelect.previewRegex && fileSelect.index >= 0 && formData.tasklist[fileSelect.index]" style="font-weight: 600; font-family: inherit; letter-spacing: normal;">{{ formData.tasklist[fileSelect.index].use_sequence_naming ? '顺序命名预览' : (formData.tasklist[fileSelect.index].use_episode_naming ? '剧集命名预览' : '正则命名预览') }}</span>
|
||||
<span v-else-if="fileSelect.previewRegex && fileSelect.index === -1" style="font-weight: 600; font-family: inherit; letter-spacing: normal;">{{ fileManager.use_sequence_naming ? '顺序命名预览' : (fileManager.use_episode_naming ? '剧集命名预览' : '正则命名预览') }}</span>
|
||||
<span v-else-if="fileSelect.selectDir" style="font-weight: 600; font-family: inherit; letter-spacing: normal;">选择{{fileSelect.selectShare ? '需转存的' : '保存到的'}}文件夹</span>
|
||||
<span v-else-if="fileSelect.selectDir" style="font-weight: 600; font-family: inherit; letter-spacing: normal;">选择{{fileSelect.moveMode ? '移动到的' : (fileSelect.selectShare ? '需转存的' : '保存到的')}}文件夹</span>
|
||||
<span v-else style="font-weight: 600; font-family: inherit; letter-spacing: normal;">选择起始文件</span>
|
||||
<div v-if="modalLoading" class="spinner-border spinner-border-sm m-1" role="status"></div>
|
||||
</h5>
|
||||
@ -1328,13 +1338,13 @@
|
||||
<!-- 文件列表 -->
|
||||
<div class="mb-3" v-if="fileSelect.previewRegex">
|
||||
<!-- 任务配置的命名预览 -->
|
||||
<div v-if="fileSelect.index >= 0">
|
||||
<div v-if="fileSelect.index >= 0 && formData.tasklist[fileSelect.index]">
|
||||
<div v-if="formData.tasklist[fileSelect.index].use_sequence_naming">
|
||||
<div style="margin-bottom: 1px; padding-left: 12px; display: flex; align-items: center;">
|
||||
<span style="font-weight: 600; font-family: inherit; letter-spacing: normal;">顺序命名表达式:</span><span class="badge badge-info" v-html="formData.tasklist[fileSelect.index].pattern"></span>
|
||||
</div>
|
||||
<div style="padding-left: 12px; display: flex; align-items: center;">
|
||||
<span class="text-muted" style="font-family: inherit; letter-spacing: normal;">预览结果仅供参考,新文件序号将基于网盘中已有文件的最大序号递增。若分享链接文件不完整,实际结果可能有所不同。</span>
|
||||
<span class="text-muted" style="font-family: inherit; letter-spacing: normal;">预览结果仅供参考,新文件序号将基于网盘或转存记录中已有文件的最大序号递增。若分享链接文件不完整,实际结果可能有所不同。</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="formData.tasklist[fileSelect.index].use_episode_naming">
|
||||
@ -1385,7 +1395,7 @@
|
||||
<template v-if="!fileSelect.previewRegex">
|
||||
<th scope="col" class="col-size cursor-pointer" style="font-family: inherit; letter-spacing: normal;" @click="sortFileList('size')">大小 <i v-if="fileSelect.sortBy === 'size'" :class="fileSelect.sortOrder === 'asc' ? 'bi bi-arrow-up' : 'bi bi-arrow-down'"></i></th>
|
||||
<th scope="col" class="col-date cursor-pointer" style="font-family: inherit; letter-spacing: normal;" @click="sortFileList('updated_at')">修改日期 <i v-if="fileSelect.sortBy === 'updated_at'" :class="fileSelect.sortOrder === 'asc' ? 'bi bi-arrow-up' : 'bi bi-arrow-down'"></i></th>
|
||||
<th scope="col" class="col-action" v-if="!fileSelect.selectShare" style="font-family: inherit; letter-spacing: normal;">操作</th>
|
||||
<th scope="col" class="col-action" v-if="!fileSelect.selectShare && !fileSelect.moveMode" style="font-family: inherit; letter-spacing: normal;">操作</th>
|
||||
</template>
|
||||
</tr>
|
||||
</thead>
|
||||
@ -1440,7 +1450,7 @@
|
||||
<td class="col-size" v-if="file.dir" style="vertical-align: top;">{{ file.include_items }} 项</td>
|
||||
<td class="col-size" v-else style="vertical-align: top;">{{file.size | size}}</td>
|
||||
<td class="col-date" style="vertical-align: top;">{{file.updated_at | ts2date}}</td>
|
||||
<td class="col-action" v-if="!fileSelect.selectShare" style="vertical-align: top;">
|
||||
<td class="col-action" v-if="!fileSelect.selectShare && !fileSelect.moveMode" style="vertical-align: top;">
|
||||
<a @click.stop.prevent="deleteSelectedFiles(file.fid, file.file_name, file.dir)" style="cursor: pointer;">删除文件</a>
|
||||
<a @click.stop.prevent="deleteSelectedFiles(file.fid, file.file_name, file.dir, true)" style="cursor: pointer; margin-left: 10px;">删除文件和记录</a>
|
||||
</td>
|
||||
@ -1454,8 +1464,12 @@
|
||||
<div class="file-selection-info mr-auto" style="color: var(--dark-text-color); font-size: 0.875rem; line-height: 1.5;">
|
||||
共 {{ fileSelect.fileList.length }} 个项目<span v-if="fileSelect.selectedFiles.length > 0">,已选中 {{ fileSelect.selectedFiles.length }} 个项目</span>
|
||||
</div>
|
||||
<button type="button" class="btn btn-primary btn-sm" @click="selectCurrentFolder()">{{fileSelect.selectShare ? '转存当前文件夹' : '保存到当前文件夹'}}</button>
|
||||
<button type="button" class="btn btn-primary btn-sm" v-if="!fileSelect.selectShare" @click="selectCurrentFolder(true)">保存到当前位置的「<span class="badge badge-light" v-html="formData.tasklist[fileSelect.index].taskname"></span>」文件夹</button>
|
||||
<button type="button" class="btn btn-primary btn-cancel" @click="$('#fileSelectModal').modal('hide')" v-if="fileSelect.moveMode">取消</button>
|
||||
<button type="button" class="btn btn-primary btn-sm" @click="selectCurrentFolder()">
|
||||
<span v-if="fileSelect.moveMode">移动到当前文件夹</span>
|
||||
<span v-else>{{fileSelect.selectShare ? '转存当前文件夹' : '保存到当前文件夹'}}</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary btn-sm" v-if="!fileSelect.selectShare && !fileSelect.moveMode && fileSelect.index !== null && fileSelect.index >= 0 && formData.tasklist[fileSelect.index]" @click="selectCurrentFolder(true)">保存到当前位置的「<span class="badge badge-light" v-html="formData.tasklist[fileSelect.index].taskname"></span>」文件夹</button>
|
||||
</div>
|
||||
<div class="modal-footer" v-if="fileSelect.previewRegex && fileSelect.index === -1">
|
||||
<div class="file-selection-info mr-auto" style="color: var(--dark-text-color); font-size: 0.875rem; line-height: 1.5;">
|
||||
@ -1571,7 +1585,9 @@
|
||||
sortOrder: "desc", // 默认排序顺序
|
||||
selectedFiles: [], // 存储选中的文件ID
|
||||
lastSelectedFileIndex: -1, // 记录最后选择的文件索引
|
||||
canUndoRename: false
|
||||
canUndoRename: false,
|
||||
moveMode: false, // 是否为移动文件模式
|
||||
moveFileIds: [] // 要移动的文件ID列表
|
||||
},
|
||||
historyParams: {
|
||||
sortBy: "transfer_time",
|
||||
@ -1700,7 +1716,7 @@
|
||||
}
|
||||
});
|
||||
|
||||
return [...taskNames].sort();
|
||||
return this.sortTaskNamesByPinyin([...taskNames]);
|
||||
},
|
||||
taskNames() {
|
||||
// 从任务列表中提取唯一的任务名称
|
||||
@ -1715,7 +1731,7 @@
|
||||
}
|
||||
});
|
||||
|
||||
return [...taskNames].sort();
|
||||
return this.sortTaskNamesByPinyin([...taskNames]);
|
||||
},
|
||||
totalPages() {
|
||||
// 直接使用后端返回的total_pages
|
||||
@ -1822,6 +1838,9 @@
|
||||
$('#fileSelectModal').on('hidden.bs.modal', () => {
|
||||
this.fileSelect.selectedFiles = [];
|
||||
this.fileSelect.lastSelectedFileIndex = -1;
|
||||
// 重置移动模式相关参数
|
||||
this.fileSelect.moveMode = false;
|
||||
this.fileSelect.moveFileIds = [];
|
||||
});
|
||||
|
||||
// 检查本地存储中的标签页状态
|
||||
@ -1882,6 +1901,9 @@
|
||||
$('#fileSelectModal').on('hidden.bs.modal', () => {
|
||||
this.fileSelect.selectedFiles = [];
|
||||
this.fileSelect.lastSelectedFileIndex = -1;
|
||||
// 重置移动模式相关参数
|
||||
this.fileSelect.moveMode = false;
|
||||
this.fileSelect.moveFileIds = [];
|
||||
});
|
||||
|
||||
window.addEventListener('beforeunload', this.handleBeforeUnload);
|
||||
@ -1953,6 +1975,14 @@
|
||||
document.removeEventListener('click', this.handleOutsideClick);
|
||||
},
|
||||
methods: {
|
||||
// 拼音排序辅助函数
|
||||
sortTaskNamesByPinyin(taskNames) {
|
||||
return taskNames.sort((a, b) => {
|
||||
const aKey = pinyinPro.pinyin(a, { toneType: 'none', type: 'string' }).toLowerCase();
|
||||
const bKey = pinyinPro.pinyin(b, { toneType: 'none', type: 'string' }).toLowerCase();
|
||||
return aKey > bKey ? 1 : -1;
|
||||
});
|
||||
},
|
||||
// 添加格式化分享链接警告信息的方法
|
||||
formatShareUrlBanMessage(message) {
|
||||
if (!message) return message;
|
||||
@ -2052,9 +2082,19 @@
|
||||
// 重置页码并切换到新账号的最后访问目录
|
||||
this.fileManager.currentPage = 1;
|
||||
this.loadFileListWithFallback(newAccountLastFolder);
|
||||
|
||||
// 如果移动文件模态框正在显示,需要重新加载目录
|
||||
if ($('#fileSelectModal').hasClass('show')) {
|
||||
const modalType = document.getElementById('fileSelectModal').getAttribute('data-modal-type');
|
||||
if (modalType === 'move') {
|
||||
// 重置路径并重新加载根目录
|
||||
this.fileSelect.paths = [];
|
||||
this.getSavepathDetail(0);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
refreshCurrentFolderCache() {
|
||||
refreshCurrentFolderCache(retryCount = 0) {
|
||||
// 刷新当前目录的缓存,强制重新请求最新的文件列表
|
||||
// 调用后端接口,添加强制刷新参数
|
||||
const params = {
|
||||
@ -2064,7 +2104,8 @@
|
||||
page_size: this.fileManager.pageSize,
|
||||
page: this.fileManager.currentPage,
|
||||
account_index: this.fileManager.selectedAccountIndex,
|
||||
force_refresh: true // 强制刷新参数
|
||||
force_refresh: true, // 强制刷新参数
|
||||
timestamp: Date.now() // 添加时间戳避免缓存
|
||||
};
|
||||
|
||||
axios.get('/file_list', { params })
|
||||
@ -2076,16 +2117,163 @@
|
||||
this.fileManager.paths = response.data.data.paths || [];
|
||||
this.fileManager.gotoPage = this.fileManager.currentPage;
|
||||
// 移除成功通知
|
||||
} else {
|
||||
this.showToast('刷新失败:' + response.data.message);
|
||||
}
|
||||
|
||||
// 检测当前的命名模式
|
||||
this.detectFileManagerNamingMode();
|
||||
} else {
|
||||
// 如果刷新失败且重试次数少于2次,则重试
|
||||
if (retryCount < 2) {
|
||||
setTimeout(() => {
|
||||
this.refreshCurrentFolderCache(retryCount + 1);
|
||||
}, 1000);
|
||||
} else {
|
||||
this.showToast('刷新失败:' + response.data.message);
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('刷新缓存失败:', error);
|
||||
// 如果网络错误且重试次数少于2次,则重试
|
||||
if (retryCount < 2) {
|
||||
setTimeout(() => {
|
||||
this.refreshCurrentFolderCache(retryCount + 1);
|
||||
}, 1000);
|
||||
} else {
|
||||
this.showToast('刷新缓存失败,请稍后重试');
|
||||
}
|
||||
});
|
||||
},
|
||||
// 获取指定文件夹的上级目录ID
|
||||
getParentFolderId(folderId) {
|
||||
// 如果是根目录,没有上级目录
|
||||
if (folderId === 'root' || folderId === '0') {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 如果是当前目录(源目录),从fileManager.paths中获取上级目录
|
||||
if (folderId === this.fileManager.currentFolder || folderId === (this.fileManager.currentFolder || "0")) {
|
||||
if (this.fileManager.paths.length === 0) {
|
||||
// 当前目录是根目录的直接子目录,上级是根目录
|
||||
return 'root';
|
||||
} else {
|
||||
// 当前目录的上级是paths中的最后一个目录
|
||||
return this.fileManager.paths[this.fileManager.paths.length - 1].fid;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果是目标目录,从fileSelect.paths中获取上级目录
|
||||
if (this.fileSelect.paths && this.fileSelect.paths.length > 0) {
|
||||
// 目标目录就是fileSelect.paths的最后一个目录,其上级目录是倒数第二个
|
||||
if (this.fileSelect.paths.length === 1) {
|
||||
// 目标目录是根目录的直接子目录
|
||||
return 'root';
|
||||
} else {
|
||||
// 目标目录的上级是paths中的倒数第二个目录
|
||||
return this.fileSelect.paths[this.fileSelect.paths.length - 2].fid;
|
||||
}
|
||||
} else {
|
||||
// 如果fileSelect.paths为空,说明目标目录是根目录,没有上级
|
||||
return null;
|
||||
}
|
||||
},
|
||||
// 刷新指定文件夹的缓存
|
||||
refreshFolderCache(folderId) {
|
||||
// 刷新指定目录的缓存,强制重新请求最新的文件列表
|
||||
// 调用后端接口,添加强制刷新参数
|
||||
const params = {
|
||||
folder_id: folderId || 'root',
|
||||
sort_by: this.fileManager.sortBy,
|
||||
order: this.fileManager.sortOrder,
|
||||
page_size: this.fileManager.pageSize,
|
||||
page: 1, // 使用第一页来刷新缓存
|
||||
account_index: this.fileManager.selectedAccountIndex,
|
||||
force_refresh: true // 强制刷新参数
|
||||
};
|
||||
|
||||
axios.get('/file_list', { params })
|
||||
.then(response => {
|
||||
// 如果刷新的是当前显示的文件夹,则更新显示
|
||||
if (folderId === this.fileManager.currentFolder) {
|
||||
if (response.data.success) {
|
||||
this.fileManager.fileList = response.data.data.list;
|
||||
this.fileManager.total = response.data.data.total;
|
||||
this.fileManager.totalPages = Math.ceil(response.data.data.total / this.fileManager.pageSize);
|
||||
this.fileManager.paths = response.data.data.paths || [];
|
||||
this.fileManager.gotoPage = this.fileManager.currentPage;
|
||||
|
||||
// 检测当前的命名模式
|
||||
this.detectFileManagerNamingMode();
|
||||
} else {
|
||||
this.showToast('刷新失败:' + response.data.message);
|
||||
}
|
||||
}
|
||||
// 如果不是当前文件夹,只是刷新缓存,不更新显示
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('刷新文件夹缓存失败:', error);
|
||||
// 只有在刷新当前文件夹时才显示错误提示
|
||||
if (folderId === this.fileManager.currentFolder) {
|
||||
this.showToast('刷新缓存失败,请稍后重试');
|
||||
}
|
||||
});
|
||||
},
|
||||
// 新建文件夹
|
||||
createNewFolder() {
|
||||
// 检查是否有文件正在编辑状态
|
||||
const editingFile = this.fileManager.fileList.find(file => file._editing);
|
||||
if (editingFile) {
|
||||
// 如果有其他文件正在编辑,先保存编辑状态
|
||||
this.saveRenameFile(editingFile);
|
||||
return;
|
||||
}
|
||||
|
||||
// 生成带时间戳的文件夹名称
|
||||
const timestamp = new Date().toLocaleString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit'
|
||||
}).replace(/[\/\s:]/g, '');
|
||||
const folderName = `新建文件夹 ${timestamp}`;
|
||||
|
||||
// 调用后端API创建文件夹
|
||||
axios.post('/create_folder', {
|
||||
parent_folder_id: this.fileManager.currentFolder || '0',
|
||||
folder_name: folderName,
|
||||
account_index: this.fileManager.selectedAccountIndex
|
||||
})
|
||||
.then(response => {
|
||||
if (response.data.success) {
|
||||
// 创建成功,将新文件夹添加到文件列表
|
||||
const newFolder = response.data.data;
|
||||
|
||||
// 设置编辑状态
|
||||
this.$set(newFolder, '_editing', true);
|
||||
this.$set(newFolder, '_editingName', newFolder.file_name);
|
||||
|
||||
// 将新文件夹添加到列表开头(文件夹通常显示在前面)
|
||||
this.fileManager.fileList.unshift(newFolder);
|
||||
this.fileManager.total += 1;
|
||||
|
||||
// 下一个tick后聚焦输入框并全选文本
|
||||
this.$nextTick(() => {
|
||||
const input = this.$refs['renameInput_' + newFolder.fid];
|
||||
if (input) {
|
||||
input.focus();
|
||||
input.select();
|
||||
}
|
||||
});
|
||||
|
||||
this.showToast('文件夹创建成功');
|
||||
} else {
|
||||
this.showToast(response.data.message || '创建文件夹失败');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('创建文件夹失败:', error);
|
||||
this.showToast('创建文件夹失败');
|
||||
});
|
||||
},
|
||||
// 添加一个检查分享链接状态的方法
|
||||
@ -3106,9 +3294,9 @@
|
||||
|
||||
// 根据模态框类型决定使用哪个账号
|
||||
// 任务配置相关的模态框始终使用主账号(索引0)
|
||||
// 文件整理页面的预览模态框使用选中的账号
|
||||
// 文件整理页面的预览模态框和移动文件模态框使用选中的账号
|
||||
const modalType = document.getElementById('fileSelectModal').getAttribute('data-modal-type');
|
||||
const accountIndex = (modalType === 'preview-filemanager') ? this.fileManager.selectedAccountIndex : 0;
|
||||
const accountIndex = (modalType === 'preview-filemanager' || modalType === 'move') ? this.fileManager.selectedAccountIndex : 0;
|
||||
|
||||
// 添加账号索引参数
|
||||
if (typeof params === 'object' && params !== null) {
|
||||
@ -3152,7 +3340,7 @@
|
||||
this.fileSelect.fileList = [];
|
||||
this.fileSelect.paths = [];
|
||||
this.fileSelect.index = index;
|
||||
// 重置排序状态为默认值
|
||||
// 重置排序状态为默认值 - 选择需转存的文件夹模态框默认修改时间倒序
|
||||
this.fileSelect.sortBy = "updated_at";
|
||||
this.fileSelect.sortOrder = "desc";
|
||||
|
||||
@ -3171,21 +3359,43 @@
|
||||
},
|
||||
getShareDetail(retryCount = 0, maxRetries = 1) {
|
||||
this.modalLoading = true;
|
||||
|
||||
// 检查index是否有效,如果无效则使用默认值
|
||||
let regexConfig = {};
|
||||
if (this.fileSelect.index !== null && this.fileSelect.index >= 0 && this.formData.tasklist[this.fileSelect.index]) {
|
||||
const task = this.formData.tasklist[this.fileSelect.index];
|
||||
regexConfig = {
|
||||
pattern: task.pattern,
|
||||
replace: task.replace,
|
||||
taskname: task.taskname,
|
||||
filterwords: task.filterwords,
|
||||
magic_regex: this.formData.magic_regex,
|
||||
use_sequence_naming: task.use_sequence_naming,
|
||||
sequence_naming: task.sequence_naming,
|
||||
use_episode_naming: task.use_episode_naming,
|
||||
episode_naming: task.episode_naming,
|
||||
episode_patterns: this.formData.episode_patterns
|
||||
};
|
||||
} else {
|
||||
// 使用默认配置
|
||||
regexConfig = {
|
||||
pattern: '',
|
||||
replace: '',
|
||||
taskname: '',
|
||||
filterwords: '',
|
||||
magic_regex: this.formData.magic_regex,
|
||||
use_sequence_naming: false,
|
||||
sequence_naming: '',
|
||||
use_episode_naming: false,
|
||||
episode_naming: '',
|
||||
episode_patterns: this.formData.episode_patterns
|
||||
};
|
||||
}
|
||||
|
||||
axios.post('/get_share_detail', {
|
||||
shareurl: this.fileSelect.shareurl,
|
||||
stoken: this.fileSelect.stoken,
|
||||
regex: {
|
||||
pattern: this.formData.tasklist[this.fileSelect.index].pattern,
|
||||
replace: this.formData.tasklist[this.fileSelect.index].replace,
|
||||
taskname: this.formData.tasklist[this.fileSelect.index].taskname,
|
||||
filterwords: this.formData.tasklist[this.fileSelect.index].filterwords,
|
||||
magic_regex: this.formData.magic_regex,
|
||||
use_sequence_naming: this.formData.tasklist[this.fileSelect.index].use_sequence_naming,
|
||||
sequence_naming: this.formData.tasklist[this.fileSelect.index].sequence_naming,
|
||||
use_episode_naming: this.formData.tasklist[this.fileSelect.index].use_episode_naming,
|
||||
episode_naming: this.formData.tasklist[this.fileSelect.index].episode_naming,
|
||||
episode_patterns: this.formData.episode_patterns
|
||||
}
|
||||
regex: regexConfig
|
||||
}).then(response => {
|
||||
if (response.data.success) {
|
||||
this.fileSelect.fileList = response.data.data.list;
|
||||
@ -3239,10 +3449,14 @@
|
||||
// 命名预览模式下使用重命名列倒序排序
|
||||
this.fileSelect.sortBy = "file_name_re";
|
||||
this.fileSelect.sortOrder = "desc";
|
||||
} else {
|
||||
// 其他情况使用修改日期倒序排序
|
||||
} else if (this.fileSelect.selectDir) {
|
||||
// 选择需转存的文件夹模态框:默认修改时间倒序
|
||||
this.fileSelect.sortBy = "updated_at";
|
||||
this.fileSelect.sortOrder = "desc";
|
||||
} else {
|
||||
// 选择起始文件模态框:使用文件名倒序排序(使用全局文件排序函数)
|
||||
this.fileSelect.sortBy = "file_name";
|
||||
this.fileSelect.sortOrder = "desc";
|
||||
}
|
||||
if (this.getShareurl(this.fileSelect.shareurl) != this.getShareurl(this.formData.tasklist[index].shareurl)) {
|
||||
this.fileSelect.stoken = "";
|
||||
@ -3311,24 +3525,122 @@
|
||||
}
|
||||
},
|
||||
selectCurrentFolder(addTaskname = false) {
|
||||
if (this.fileSelect.selectShare) {
|
||||
if (this.fileSelect.moveMode) {
|
||||
// 移动文件模式
|
||||
this.moveFilesToCurrentFolder();
|
||||
} else if (this.fileSelect.selectShare) {
|
||||
// 检查index是否有效
|
||||
if (this.fileSelect.index !== null && this.fileSelect.index >= 0 && this.formData.tasklist[this.fileSelect.index]) {
|
||||
this.formData.tasklist[this.fileSelect.index].shareurl_ban = undefined;
|
||||
// 如果是分享文件夹且文件列表为空,则设置shareurl_ban
|
||||
if (!this.fileSelect.fileList || this.fileSelect.fileList.length === 0) {
|
||||
this.$set(this.formData.tasklist[this.fileSelect.index], "shareurl_ban", "该分享已被删除,无法访问");
|
||||
}
|
||||
this.formData.tasklist[this.fileSelect.index].shareurl = this.fileSelect.shareurl;
|
||||
}
|
||||
} else {
|
||||
// 检查index是否有效
|
||||
if (this.fileSelect.index !== null && this.fileSelect.index >= 0 && this.formData.tasklist[this.fileSelect.index]) {
|
||||
// 去掉前导斜杠,避免双斜杠问题
|
||||
this.formData.tasklist[this.fileSelect.index].savepath = this.fileSelect.paths.map(item => item.name).join("/");
|
||||
if (addTaskname) {
|
||||
this.formData.tasklist[this.fileSelect.index].savepath += "/" + this.formData.tasklist[this.fileSelect.index].taskname
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!this.fileSelect.moveMode) {
|
||||
$('#fileSelectModal').modal('hide')
|
||||
}
|
||||
},
|
||||
// 移动文件到当前文件夹
|
||||
moveFilesToCurrentFolder() {
|
||||
if (!this.fileSelect.moveFileIds || this.fileSelect.moveFileIds.length === 0) {
|
||||
alert('没有选择要移动的文件');
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取目标文件夹ID
|
||||
const targetFolderId = this.fileSelect.paths.length > 0
|
||||
? this.fileSelect.paths[this.fileSelect.paths.length - 1].fid
|
||||
: "0";
|
||||
|
||||
// 记录源目录ID(文件原本所在的目录)
|
||||
const sourceFolderId = this.fileManager.currentFolder || "0";
|
||||
|
||||
const fileCount = this.fileSelect.moveFileIds.length;
|
||||
const confirmMessage = fileCount === 1
|
||||
? '确定要移动此文件吗?'
|
||||
: `确定要移动选中的 ${fileCount} 个文件吗?`;
|
||||
|
||||
if (!confirm(confirmMessage)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 调用移动文件API
|
||||
axios.post('/move_file', {
|
||||
file_ids: this.fileSelect.moveFileIds,
|
||||
target_folder_id: targetFolderId,
|
||||
account_index: this.fileManager.selectedAccountIndex
|
||||
})
|
||||
.then(response => {
|
||||
if (response.data.success) {
|
||||
this.showToast(`成功移动 ${response.data.moved_count} 个文件`);
|
||||
|
||||
// 关闭模态框
|
||||
$('#fileSelectModal').modal('hide');
|
||||
|
||||
// 重置移动模式相关参数
|
||||
this.fileSelect.moveMode = false;
|
||||
this.fileSelect.moveFileIds = [];
|
||||
this.fileSelect.index = null; // 重置index避免后续访问undefined
|
||||
|
||||
// 清空选中的文件
|
||||
this.fileManager.selectedFiles = [];
|
||||
|
||||
// 延迟刷新以确保后端处理完成
|
||||
setTimeout(() => {
|
||||
// 刷新当前页面显示(源目录)
|
||||
this.refreshCurrentFolderCache();
|
||||
|
||||
// 如果目标目录不同于源目录,也刷新目标目录的缓存
|
||||
if (targetFolderId !== sourceFolderId) {
|
||||
this.refreshFolderCache(targetFolderId); // 刷新目标目录缓存
|
||||
}
|
||||
|
||||
// 刷新源目录的上级目录(更新源文件夹的项目数量)
|
||||
const sourceParentFolderId = this.getParentFolderId(sourceFolderId);
|
||||
if (sourceParentFolderId !== null) {
|
||||
this.refreshFolderCache(sourceParentFolderId);
|
||||
}
|
||||
|
||||
// 刷新目标目录的上级目录(更新目标文件夹的项目数量)
|
||||
if (targetFolderId !== sourceFolderId) {
|
||||
const targetParentFolderId = this.getParentFolderId(targetFolderId);
|
||||
if (targetParentFolderId !== null && targetParentFolderId !== sourceParentFolderId) {
|
||||
this.refreshFolderCache(targetParentFolderId);
|
||||
}
|
||||
}
|
||||
}, 500); // 延迟500ms确保后端处理完成
|
||||
} else {
|
||||
const errorMessage = response.data.message || '移动失败';
|
||||
// 对于"不能移动至相同目录"错误使用Toast通知
|
||||
if (errorMessage.includes('不能移动至相同目录')) {
|
||||
this.showToast('不能移动至相同目录');
|
||||
} else {
|
||||
this.showToast(errorMessage);
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('移动文件失败:', error);
|
||||
this.showToast('移动文件失败');
|
||||
});
|
||||
},
|
||||
selectStartFid(fid) {
|
||||
// 检查index是否有效
|
||||
if (this.fileSelect.index !== null && this.fileSelect.index >= 0 && this.formData.tasklist[this.fileSelect.index]) {
|
||||
Vue.set(this.formData.tasklist[this.fileSelect.index], 'startfid', fid);
|
||||
}
|
||||
$('#fileSelectModal').modal('hide')
|
||||
},
|
||||
getShareurl(shareurl, path = {}) {
|
||||
@ -3705,7 +4017,7 @@
|
||||
}
|
||||
}).then(response => {
|
||||
if (response.data.success && response.data.data.all_task_names) {
|
||||
this.allTaskNames = response.data.data.all_task_names.sort();
|
||||
this.allTaskNames = this.sortTaskNamesByPinyin(response.data.data.all_task_names);
|
||||
} else {
|
||||
// 如果API失败,回退到从当前页记录中提取任务名称
|
||||
if (this.history.records && this.history.records.length > 0) {
|
||||
@ -3715,7 +4027,7 @@
|
||||
taskNames.add(record.task_name);
|
||||
}
|
||||
});
|
||||
this.allTaskNames = [...taskNames].sort();
|
||||
this.allTaskNames = this.sortTaskNamesByPinyin([...taskNames]);
|
||||
}
|
||||
}
|
||||
}).catch(error => {
|
||||
@ -3727,7 +4039,7 @@
|
||||
taskNames.add(record.task_name);
|
||||
}
|
||||
});
|
||||
this.allTaskNames = [...taskNames].sort();
|
||||
this.allTaskNames = this.sortTaskNamesByPinyin([...taskNames]);
|
||||
}
|
||||
});
|
||||
},
|
||||
@ -3859,14 +4171,27 @@
|
||||
// 文件夹始终在前
|
||||
if (a.dir && !b.dir) return -1;
|
||||
if (!a.dir && b.dir) return 1;
|
||||
let aValue = a.file_name.toLowerCase();
|
||||
let bValue = b.file_name.toLowerCase();
|
||||
|
||||
// 检查当前模态框类型,选择起始文件模态框使用全局文件排序函数
|
||||
const modalType = document.getElementById('fileSelectModal').getAttribute('data-modal-type');
|
||||
if (modalType === 'start-file') {
|
||||
// 选择起始文件模态框:使用全局文件排序函数
|
||||
const ka = sortFileByName(a), kb = sortFileByName(b);
|
||||
for (let i = 0; i < ka.length; ++i) {
|
||||
if (ka[i] !== kb[i]) return this.fileSelect.sortOrder === 'asc' ? (ka[i] > kb[i] ? 1 : -1) : (ka[i] < kb[i] ? 1 : -1);
|
||||
}
|
||||
return 0;
|
||||
} else {
|
||||
// 其他模态框:使用拼音排序
|
||||
let aValue = pinyinPro.pinyin(a.file_name, { toneType: 'none', type: 'string' }).toLowerCase();
|
||||
let bValue = pinyinPro.pinyin(b.file_name, { toneType: 'none', type: 'string' }).toLowerCase();
|
||||
if (this.fileSelect.sortOrder === 'asc') {
|
||||
return aValue > bValue ? 1 : -1;
|
||||
} else {
|
||||
return aValue < bValue ? 1 : -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (field === 'file_name_re') {
|
||||
const aHasValidRename = a.file_name_re && a.file_name_re !== '×' && !a.file_name_re.startsWith('×');
|
||||
const bHasValidRename = b.file_name_re && b.file_name_re !== '×' && !b.file_name_re.startsWith('×');
|
||||
@ -3884,12 +4209,19 @@
|
||||
let aValue, bValue;
|
||||
// 对于重命名列,优先使用episode_number进行数值排序(如果存在)
|
||||
if (a.episode_number !== undefined && b.episode_number !== undefined) {
|
||||
aValue = a.episode_number;
|
||||
bValue = b.episode_number;
|
||||
// 确保进行数值比较
|
||||
aValue = parseInt(a.episode_number, 10);
|
||||
bValue = parseInt(b.episode_number, 10);
|
||||
|
||||
// 如果解析失败,回退到字符串比较
|
||||
if (isNaN(aValue) || isNaN(bValue)) {
|
||||
aValue = String(a.episode_number);
|
||||
bValue = String(b.episode_number);
|
||||
}
|
||||
} else {
|
||||
// 否则使用重命名后的文件名进行字符串排序
|
||||
aValue = (a.file_name_re || '').toLowerCase();
|
||||
bValue = (b.file_name_re || '').toLowerCase();
|
||||
// 否则使用重命名后的文件名进行拼音排序
|
||||
aValue = pinyinPro.pinyin(a.file_name_re || '', { toneType: 'none', type: 'string' }).toLowerCase();
|
||||
bValue = pinyinPro.pinyin(b.file_name_re || '', { toneType: 'none', type: 'string' }).toLowerCase();
|
||||
}
|
||||
|
||||
if (this.fileSelect.sortOrder === 'asc') {
|
||||
@ -3934,8 +4266,9 @@
|
||||
if (a.dir && !b.dir) return -1;
|
||||
if (!a.dir && b.dir) return 1;
|
||||
|
||||
let aValue = a.dir ? 0 : (a.size || 0);
|
||||
let bValue = b.dir ? 0 : (b.size || 0);
|
||||
// 文件夹按项目数量排序,文件按大小排序
|
||||
let aValue = a.dir ? (a.include_items || 0) : (a.size || 0);
|
||||
let bValue = b.dir ? (b.include_items || 0) : (b.size || 0);
|
||||
if (this.fileSelect.sortOrder === 'asc') {
|
||||
return aValue > bValue ? 1 : -1;
|
||||
} else {
|
||||
@ -3958,13 +4291,12 @@
|
||||
if (field === 'file_name') {
|
||||
if (a.dir && !b.dir) return -1;
|
||||
if (!a.dir && b.dir) return 1;
|
||||
let aValue = a.file_name.toLowerCase();
|
||||
let bValue = b.file_name.toLowerCase();
|
||||
if (order === 'asc') {
|
||||
return aValue > bValue ? 1 : -1;
|
||||
} else {
|
||||
return aValue < bValue ? 1 : -1;
|
||||
// 使用智能排序函数
|
||||
const ka = sortFileByName(a), kb = sortFileByName(b);
|
||||
for (let i = 0; i < ka.length; ++i) {
|
||||
if (ka[i] !== kb[i]) return order === 'asc' ? (ka[i] > kb[i] ? 1 : -1) : (ka[i] < kb[i] ? 1 : -1);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (field === 'file_name_re') {
|
||||
const aHasValidRename = a.file_name_re && a.file_name_re !== '×' && !a.file_name_re.startsWith('×');
|
||||
@ -3983,12 +4315,19 @@
|
||||
let aValue, bValue;
|
||||
// 对于重命名列,优先使用episode_number进行数值排序(如果存在)
|
||||
if (a.episode_number !== undefined && b.episode_number !== undefined) {
|
||||
aValue = a.episode_number;
|
||||
bValue = b.episode_number;
|
||||
// 确保进行数值比较
|
||||
aValue = parseInt(a.episode_number, 10);
|
||||
bValue = parseInt(b.episode_number, 10);
|
||||
|
||||
// 如果解析失败,回退到字符串比较
|
||||
if (isNaN(aValue) || isNaN(bValue)) {
|
||||
aValue = String(a.episode_number);
|
||||
bValue = String(b.episode_number);
|
||||
}
|
||||
} else {
|
||||
// 否则使用重命名后的文件名进行字符串排序
|
||||
aValue = (a.file_name_re || '').toLowerCase();
|
||||
bValue = (b.file_name_re || '').toLowerCase();
|
||||
// 否则使用重命名后的文件名进行拼音排序
|
||||
aValue = pinyinPro.pinyin(a.file_name_re || '', { toneType: 'none', type: 'string' }).toLowerCase();
|
||||
bValue = pinyinPro.pinyin(b.file_name_re || '', { toneType: 'none', type: 'string' }).toLowerCase();
|
||||
}
|
||||
|
||||
if (order === 'asc') {
|
||||
@ -4033,8 +4372,9 @@
|
||||
if (a.dir && !b.dir) return -1;
|
||||
if (!a.dir && b.dir) return 1;
|
||||
|
||||
let aValue = a.dir ? 0 : (a.size || 0);
|
||||
let bValue = b.dir ? 0 : (b.size || 0);
|
||||
// 文件夹按项目数量排序,文件按大小排序
|
||||
let aValue = a.dir ? (a.include_items || 0) : (a.size || 0);
|
||||
let bValue = b.dir ? (b.include_items || 0) : (b.size || 0);
|
||||
if (order === 'asc') {
|
||||
return aValue > bValue ? 1 : -1;
|
||||
} else {
|
||||
@ -4890,8 +5230,11 @@
|
||||
if (previewItem) {
|
||||
// 设置重命名字段
|
||||
file.file_name_re = previewItem.new_name;
|
||||
// 设置集数字段用于排序
|
||||
file.episode_number = previewItem.episode_number;
|
||||
} else {
|
||||
file.file_name_re = null;
|
||||
file.episode_number = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
@ -4987,6 +5330,39 @@
|
||||
}
|
||||
});
|
||||
},
|
||||
// 开始移动文件
|
||||
startMoveFile(file) {
|
||||
// 如果是文件夹,不允许移动
|
||||
if (file.dir) {
|
||||
this.showToast('暂不支持移动文件夹');
|
||||
return;
|
||||
}
|
||||
|
||||
// 设置移动模式相关参数
|
||||
this.fileSelect.moveMode = true;
|
||||
this.fileSelect.moveFileIds = this.fileManager.selectedFiles.length > 0 && this.fileManager.selectedFiles.includes(file.fid)
|
||||
? this.fileManager.selectedFiles
|
||||
: [file.fid];
|
||||
this.fileSelect.selectShare = false;
|
||||
this.fileSelect.selectDir = true;
|
||||
this.fileSelect.previewRegex = false;
|
||||
this.fileSelect.error = undefined;
|
||||
this.fileSelect.fileList = [];
|
||||
this.fileSelect.paths = [];
|
||||
this.fileSelect.index = -1;
|
||||
|
||||
// 重置排序状态为默认值 - 选择移动目标文件夹模态框默认修改时间倒序
|
||||
this.fileSelect.sortBy = "updated_at";
|
||||
this.fileSelect.sortOrder = "desc";
|
||||
|
||||
// 设置模态框类型为move(移动目标文件夹)
|
||||
document.getElementById('fileSelectModal').setAttribute('data-modal-type', 'move');
|
||||
|
||||
$('#fileSelectModal').modal('show');
|
||||
|
||||
// 加载根目录
|
||||
this.getSavepathDetail(0);
|
||||
},
|
||||
// 保存重命名
|
||||
saveRenameFile(file) {
|
||||
if (!file._editing) return;
|
||||
@ -5490,6 +5866,9 @@
|
||||
$('#fileSelectModal').on('hidden.bs.modal', () => {
|
||||
this.fileSelect.selectedFiles = [];
|
||||
this.fileSelect.lastSelectedFileIndex = -1;
|
||||
// 重置移动模式相关参数
|
||||
this.fileSelect.moveMode = false;
|
||||
this.fileSelect.moveFileIds = [];
|
||||
});
|
||||
|
||||
// 检查本地存储中的标签页状态
|
||||
@ -5547,6 +5926,9 @@
|
||||
$('#fileSelectModal').on('hidden.bs.modal', () => {
|
||||
this.fileSelect.selectedFiles = [];
|
||||
this.fileSelect.lastSelectedFileIndex = -1;
|
||||
// 重置移动模式相关参数
|
||||
this.fileSelect.moveMode = false;
|
||||
this.fileSelect.moveFileIds = [];
|
||||
});
|
||||
|
||||
window.addEventListener('beforeunload', this.handleBeforeUnload);
|
||||
|
||||
89
app/utils/pinyin_sort.py
Normal file
89
app/utils/pinyin_sort.py
Normal file
@ -0,0 +1,89 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
拼音排序工具模块
|
||||
用于将中文字符串转换为拼音后进行排序
|
||||
"""
|
||||
|
||||
try:
|
||||
from pypinyin import lazy_pinyin, Style
|
||||
PYPINYIN_AVAILABLE = True
|
||||
except ImportError:
|
||||
PYPINYIN_AVAILABLE = False
|
||||
print("Warning: pypinyin not available, falling back to simple string sort")
|
||||
|
||||
def to_pinyin_for_sort(text):
|
||||
"""
|
||||
将字符串转换为拼音用于排序
|
||||
保留非中文字符,只转换中文字符为拼音
|
||||
|
||||
Args:
|
||||
text (str): 要转换的字符串
|
||||
|
||||
Returns:
|
||||
str: 转换后的拼音字符串,用于排序比较
|
||||
"""
|
||||
if not text:
|
||||
return ''
|
||||
|
||||
if PYPINYIN_AVAILABLE:
|
||||
# 使用pypinyin库进行转换
|
||||
# Style.NORMAL: 普通风格,不带声调
|
||||
# errors='default': 保留无法转换的字符
|
||||
pinyin_list = lazy_pinyin(text, style=Style.NORMAL, errors='default')
|
||||
return ''.join(pinyin_list).lower()
|
||||
else:
|
||||
# 如果pypinyin不可用,直接返回小写字符串
|
||||
return text.lower()
|
||||
|
||||
def pinyin_compare(a, b):
|
||||
"""
|
||||
拼音排序比较函数
|
||||
|
||||
Args:
|
||||
a (str): 第一个字符串
|
||||
b (str): 第二个字符串
|
||||
|
||||
Returns:
|
||||
int: 比较结果 (-1, 0, 1)
|
||||
"""
|
||||
pinyin_a = to_pinyin_for_sort(a)
|
||||
pinyin_b = to_pinyin_for_sort(b)
|
||||
|
||||
if pinyin_a < pinyin_b:
|
||||
return -1
|
||||
elif pinyin_a > pinyin_b:
|
||||
return 1
|
||||
else:
|
||||
return 0
|
||||
|
||||
def get_filename_pinyin_sort_key(filename):
|
||||
"""
|
||||
为文件名生成拼音排序键
|
||||
保持完整内容,只将汉字转换为拼音
|
||||
|
||||
Args:
|
||||
filename (str): 文件名
|
||||
|
||||
Returns:
|
||||
str: 用于排序的拼音字符串
|
||||
"""
|
||||
return to_pinyin_for_sort(filename)
|
||||
|
||||
def pinyin_sort_files(files, key_func=None, reverse=False):
|
||||
"""
|
||||
使用拼音排序对文件列表进行排序
|
||||
|
||||
Args:
|
||||
files (list): 文件列表
|
||||
key_func (callable): 从文件对象中提取文件名的函数,默认为None(假设files是字符串列表)
|
||||
reverse (bool): 是否逆序排序
|
||||
|
||||
Returns:
|
||||
list: 排序后的文件列表
|
||||
"""
|
||||
if key_func is None:
|
||||
# 假设files是字符串列表
|
||||
return sorted(files, key=to_pinyin_for_sort, reverse=reverse)
|
||||
else:
|
||||
# 使用key_func提取文件名后进行拼音排序
|
||||
return sorted(files, key=lambda x: to_pinyin_for_sort(key_func(x)), reverse=reverse)
|
||||
@ -8,15 +8,33 @@ import time
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
try:
|
||||
from quark_auto_save import sort_file_by_name
|
||||
from app.utils.pinyin_sort import get_filename_pinyin_sort_key
|
||||
except ImportError:
|
||||
# 如果无法导入,提供一个简单的排序函数作为替代
|
||||
def sort_file_by_name(file):
|
||||
if isinstance(file, dict):
|
||||
filename = file.get("file_name", "")
|
||||
update_time = file.get("updated_at", 0)
|
||||
else:
|
||||
filename = file
|
||||
# 简单排序,主要通过文件名进行
|
||||
return filename
|
||||
update_time = 0
|
||||
# 简单排序,主要通过文件名进行(使用拼音排序)
|
||||
try:
|
||||
from pypinyin import lazy_pinyin, Style
|
||||
pinyin_list = lazy_pinyin(filename, style=Style.NORMAL, errors='ignore')
|
||||
pinyin_sort_key = ''.join(pinyin_list).lower()
|
||||
except ImportError:
|
||||
pinyin_sort_key = filename.lower()
|
||||
# 返回五级排序元组:(日期, 期数, 上中下, 修改时间, 拼音排序)
|
||||
return (float('inf'), float('inf'), 0, update_time, pinyin_sort_key)
|
||||
|
||||
def get_filename_pinyin_sort_key(filename):
|
||||
try:
|
||||
from pypinyin import lazy_pinyin, Style
|
||||
pinyin_list = lazy_pinyin(filename, style=Style.NORMAL, errors='ignore')
|
||||
return ''.join(pinyin_list).lower()
|
||||
except ImportError:
|
||||
return filename.lower()
|
||||
|
||||
|
||||
class Aria2:
|
||||
|
||||
@ -42,20 +42,28 @@ def sort_file_by_name(file):
|
||||
通用的文件排序函数,用于根据文件名智能排序
|
||||
支持多种格式的日期、期数、集数等提取和排序
|
||||
使用多级排序键,按日期、期数、上中下顺序排序
|
||||
如果以上均无法提取,则使用文件更新时间作为最后排序依据
|
||||
如果以上均无法提取,则使用文件更新时间和拼音排序作为最后排序依据
|
||||
"""
|
||||
if isinstance(file, dict) and file.get("dir", False): # 跳过文件夹
|
||||
return (float('inf'), float('inf'), float('inf'), 0)
|
||||
return (float('inf'), float('inf'), float('inf'), 0, "")
|
||||
|
||||
# 获取文件名,支持字符串或文件对象
|
||||
if isinstance(file, dict):
|
||||
filename = file.get("file_name", "")
|
||||
# 获取更新时间作为最后排序依据
|
||||
# 获取更新时间作为第四级排序依据
|
||||
update_time = file.get("updated_at", 0)
|
||||
else:
|
||||
filename = file
|
||||
update_time = 0
|
||||
|
||||
# 导入拼音排序工具用于第五级排序
|
||||
try:
|
||||
from app.utils.pinyin_sort import get_filename_pinyin_sort_key
|
||||
pinyin_sort_key = get_filename_pinyin_sort_key(filename)
|
||||
except ImportError:
|
||||
# 如果导入失败,使用简单的小写排序作为备用
|
||||
pinyin_sort_key = filename.lower()
|
||||
|
||||
# 提取文件名,不含扩展名
|
||||
file_name_without_ext = os.path.splitext(filename)[0]
|
||||
|
||||
@ -212,8 +220,8 @@ def sort_file_by_name(file):
|
||||
elif re.search(r'下[集期话部篇]?|[集期话部篇]下', filename):
|
||||
segment_value = 3
|
||||
|
||||
# 返回多级排序元组,加入更新时间作为第四级排序键
|
||||
return (date_value, episode_value, segment_value, update_time)
|
||||
# 返回多级排序元组,加入更新时间作为第四级排序键,拼音排序作为第五级排序键
|
||||
return (date_value, episode_value, segment_value, update_time, pinyin_sort_key)
|
||||
|
||||
|
||||
# 全局的剧集编号提取函数
|
||||
@ -294,6 +302,16 @@ def extract_episode_number(filename, episode_patterns=None, config_data=None):
|
||||
if month and day and 1 <= month <= 12 and 1 <= day <= 31:
|
||||
filename_without_dates = filename_without_dates.replace(date_str, " ")
|
||||
|
||||
# 预处理:移除分辨率标识(如 720p, 1080P, 2160p 等)
|
||||
resolution_patterns = [
|
||||
r'\b\d+[pP]\b', # 匹配 720p, 1080P, 2160p 等
|
||||
r'\b\d+x\d+\b', # 匹配 1920x1080 等
|
||||
# 注意:不移除4K/8K,因为剧集匹配规则中有 (\d+)[-_\s]*4[Kk] 模式
|
||||
]
|
||||
|
||||
for pattern in resolution_patterns:
|
||||
filename_without_dates = re.sub(pattern, ' ', filename_without_dates)
|
||||
|
||||
# 优先匹配SxxExx格式
|
||||
match_s_e = re.search(r'[Ss](\d+)[Ee](\d+)', filename_without_dates)
|
||||
if match_s_e:
|
||||
@ -382,6 +400,9 @@ def extract_episode_number(filename, episode_patterns=None, config_data=None):
|
||||
episode_num = int(num_match.group(1))
|
||||
# 检查提取的数字是否可能是日期
|
||||
if not is_date_format(str(episode_num)):
|
||||
# 检查是否是过大的数字(可能是时间戳、文件大小等)
|
||||
if episode_num > 9999:
|
||||
return None # 跳过过大的数字
|
||||
return episode_num
|
||||
|
||||
return None
|
||||
@ -608,9 +629,28 @@ def get_file_icon(file_name, is_dir=False):
|
||||
if any(lower_name.endswith(ext) for ext in ['.srt', '.ass', '.ssa', '.vtt']):
|
||||
return "💬"
|
||||
|
||||
# 歌词文件
|
||||
if any(lower_name.endswith(ext) for ext in ['.lrc']):
|
||||
return "💬"
|
||||
|
||||
# 默认图标(其他文件类型)
|
||||
return ""
|
||||
|
||||
# 定义一个函数来去除文件名中的所有图标
|
||||
def remove_file_icons(filename):
|
||||
"""去除文件名开头的所有图标"""
|
||||
# 定义所有可能的文件图标
|
||||
icons = ["🎞️", "🖼️", "🎵", "📄", "📦", "📝", "💬", "📁"]
|
||||
|
||||
# 去除开头的图标和空格
|
||||
clean_name = filename
|
||||
for icon in icons:
|
||||
if clean_name.startswith(icon):
|
||||
clean_name = clean_name[len(icon):].lstrip()
|
||||
break
|
||||
|
||||
return clean_name
|
||||
|
||||
|
||||
class Config:
|
||||
# 下载配置
|
||||
@ -1081,6 +1121,21 @@ class Quark:
|
||||
).json()
|
||||
return response
|
||||
|
||||
def mkdir_in_folder(self, parent_fid, folder_name):
|
||||
"""在指定父目录下创建新文件夹"""
|
||||
url = f"{self.BASE_URL}/1/clouddrive/file"
|
||||
querystring = {"pr": "ucpro", "fr": "pc", "uc_param_str": ""}
|
||||
payload = {
|
||||
"pdir_fid": parent_fid,
|
||||
"file_name": folder_name,
|
||||
"dir_path": "",
|
||||
"dir_init_lock": False,
|
||||
}
|
||||
response = self._send_request(
|
||||
"POST", url, json=payload, params=querystring
|
||||
).json()
|
||||
return response
|
||||
|
||||
def rename(self, fid, file_name):
|
||||
url = f"{self.BASE_URL}/1/clouddrive/file/rename"
|
||||
querystring = {"pr": "ucpro", "fr": "pc", "uc_param_str": ""}
|
||||
@ -1099,6 +1154,21 @@ class Quark:
|
||||
).json()
|
||||
return response
|
||||
|
||||
def move(self, filelist, to_pdir_fid):
|
||||
"""移动文件到指定目录"""
|
||||
url = f"{self.BASE_URL}/1/clouddrive/file/move"
|
||||
querystring = {"pr": "ucpro", "fr": "pc", "uc_param_str": ""}
|
||||
payload = {
|
||||
"action_type": 2,
|
||||
"filelist": filelist,
|
||||
"to_pdir_fid": to_pdir_fid,
|
||||
"exclude_fids": []
|
||||
}
|
||||
response = self._send_request(
|
||||
"POST", url, json=payload, params=querystring
|
||||
).json()
|
||||
return response
|
||||
|
||||
def recycle_list(self, page=1, size=30):
|
||||
url = f"{self.BASE_URL}/1/clouddrive/file/recycle/list"
|
||||
querystring = {
|
||||
@ -1389,12 +1459,13 @@ class Quark:
|
||||
return False
|
||||
|
||||
# 添加一个专门从重命名日志更新记录的方法
|
||||
def update_transfer_record_from_log(self, task, rename_log):
|
||||
def update_transfer_record_from_log(self, task, rename_log, actual_file_names=None):
|
||||
"""从重命名日志中提取信息并更新记录
|
||||
|
||||
Args:
|
||||
task: 任务信息
|
||||
rename_log: 重命名日志,格式为 "重命名: 旧名 → 新名"
|
||||
actual_file_names: 实际文件名映射字典,用于修正显示的文件名
|
||||
"""
|
||||
try:
|
||||
# 使用字符串分割方法提取文件名,更可靠地获取完整文件名
|
||||
@ -1407,25 +1478,30 @@ class Quark:
|
||||
if " → " not in parts:
|
||||
return False
|
||||
|
||||
old_name, new_name = parts.split(" → ", 1)
|
||||
old_name, expected_new_name = parts.split(" → ", 1)
|
||||
|
||||
# 如果新名称包含"失败",则是失败的重命名,跳过
|
||||
if "失败" in new_name:
|
||||
if "失败" in expected_new_name:
|
||||
return False
|
||||
|
||||
# 处理可能的截断标记,只保留实际文件名部分
|
||||
# 注意:只有明确是失败消息才应该截断
|
||||
if " 失败," in new_name:
|
||||
new_name = new_name.split(" 失败,")[0]
|
||||
if " 失败," in expected_new_name:
|
||||
expected_new_name = expected_new_name.split(" 失败,")[0]
|
||||
|
||||
# 去除首尾空格
|
||||
old_name = old_name.strip()
|
||||
new_name = new_name.strip()
|
||||
expected_new_name = expected_new_name.strip()
|
||||
|
||||
# 确保提取到的是完整文件名
|
||||
if not old_name or not new_name:
|
||||
if not old_name or not expected_new_name:
|
||||
return False
|
||||
|
||||
# 获取实际的文件名(如果提供了映射)
|
||||
actual_new_name = expected_new_name
|
||||
if actual_file_names and old_name in actual_file_names:
|
||||
actual_new_name = actual_file_names[old_name]
|
||||
|
||||
# 初始化数据库
|
||||
db = RecordDB()
|
||||
|
||||
@ -1446,13 +1522,13 @@ class Quark:
|
||||
# 如果找到了匹配的记录,使用file_id进行更新
|
||||
file_id = result[0] if result else ""
|
||||
|
||||
# 更新记录
|
||||
# 更新记录,使用实际的文件名
|
||||
if file_id:
|
||||
# 使用file_id更新
|
||||
updated = db.update_renamed_to(
|
||||
file_id=file_id,
|
||||
original_name="", # 不使用原文件名,因为已有file_id
|
||||
renamed_to=new_name,
|
||||
renamed_to=actual_new_name, # 使用实际的文件名
|
||||
task_name=task_name,
|
||||
save_path=save_path
|
||||
)
|
||||
@ -1461,7 +1537,7 @@ class Quark:
|
||||
updated = db.update_renamed_to(
|
||||
file_id="", # 不使用file_id查询,因为在日志中无法获取
|
||||
original_name=old_name,
|
||||
renamed_to=new_name,
|
||||
renamed_to=actual_new_name, # 使用实际的文件名
|
||||
task_name=task_name,
|
||||
save_path=save_path
|
||||
)
|
||||
@ -1482,9 +1558,79 @@ class Quark:
|
||||
task: 任务信息
|
||||
rename_logs: 重命名日志列表
|
||||
"""
|
||||
# 获取实际的文件名映射
|
||||
actual_file_names = self.get_actual_file_names_from_directory(task, rename_logs)
|
||||
|
||||
for log in rename_logs:
|
||||
if "重命名:" in log and "→" in log and "失败" not in log:
|
||||
self.update_transfer_record_from_log(task, log)
|
||||
self.update_transfer_record_from_log(task, log, actual_file_names)
|
||||
|
||||
def get_actual_file_names_from_directory(self, task, rename_logs):
|
||||
"""从目录中获取实际的文件名,用于修正转存记录和日志显示
|
||||
|
||||
Args:
|
||||
task: 任务信息
|
||||
rename_logs: 重命名日志列表
|
||||
|
||||
Returns:
|
||||
dict: 原文件名到实际文件名的映射
|
||||
"""
|
||||
try:
|
||||
# 获取保存路径
|
||||
savepath = re.sub(r"/{2,}", "/", f"/{task['savepath']}")
|
||||
|
||||
# 获取当前目录的文件列表
|
||||
if hasattr(self, 'savepath_fid') and self.savepath_fid.get(savepath):
|
||||
dir_file_list = self.ls_dir(self.savepath_fid[savepath])
|
||||
else:
|
||||
return {}
|
||||
|
||||
# 从重命名日志中提取预期的重命名映射(包括失败的重命名)
|
||||
expected_renames = {}
|
||||
for log in rename_logs:
|
||||
if "重命名:" in log and " → " in log:
|
||||
parts = log.split("重命名:", 1)[1].strip()
|
||||
if " → " in parts:
|
||||
old_name, new_name = parts.split(" → ", 1)
|
||||
# 处理可能的失败信息
|
||||
if " 失败," in new_name:
|
||||
new_name = new_name.split(" 失败,")[0]
|
||||
old_name = old_name.strip()
|
||||
new_name = new_name.strip()
|
||||
expected_renames[old_name] = new_name
|
||||
|
||||
# 获取目录中实际存在的文件名
|
||||
actual_files = [f["file_name"] for f in dir_file_list if not f["dir"]]
|
||||
|
||||
# 创建实际文件名映射
|
||||
actual_renames = {}
|
||||
|
||||
# 对于每个预期的重命名,检查实际文件是否存在
|
||||
for old_name, expected_new_name in expected_renames.items():
|
||||
if expected_new_name in actual_files:
|
||||
# 预期的重命名成功了
|
||||
actual_renames[old_name] = expected_new_name
|
||||
elif old_name in actual_files:
|
||||
# 重命名失败,文件保持原名
|
||||
actual_renames[old_name] = old_name
|
||||
else:
|
||||
# 尝试模糊匹配,可能文件名有细微差异
|
||||
for actual_file in actual_files:
|
||||
# 简单的相似度检查:如果文件名包含预期名称的主要部分
|
||||
if (len(expected_new_name) > 10 and
|
||||
expected_new_name[:10] in actual_file and
|
||||
actual_file not in actual_renames.values()):
|
||||
actual_renames[old_name] = actual_file
|
||||
break
|
||||
else:
|
||||
# 如果找不到匹配,使用预期名称(可能文件已被删除或移动)
|
||||
actual_renames[old_name] = expected_new_name
|
||||
|
||||
return actual_renames
|
||||
|
||||
except Exception as e:
|
||||
print(f"获取实际文件名失败: {e}")
|
||||
return {}
|
||||
|
||||
def check_file_exists_in_records(self, file_id, task=None):
|
||||
"""检查文件ID是否存在于转存记录中
|
||||
@ -1659,6 +1805,10 @@ class Quark:
|
||||
"dir_path": [] # 保存目录路径
|
||||
}
|
||||
|
||||
# 对文件列表进行排序,使用全局文件排序函数的倒序排序
|
||||
# 这样可以确保起始文件过滤逻辑正确工作
|
||||
share_file_list.sort(key=sort_file_by_name, reverse=True)
|
||||
|
||||
# 应用过滤词过滤
|
||||
if task.get("filterwords"):
|
||||
# 记录过滤前的文件总数(包括文件夹)
|
||||
@ -1808,6 +1958,9 @@ class Quark:
|
||||
# 预先过滤掉已经存在的文件(按大小和扩展名比对)
|
||||
# 只保留文件,不保留文件夹
|
||||
filtered_share_files = []
|
||||
start_fid = task.get("startfid", "")
|
||||
start_file_found = False
|
||||
|
||||
for share_file in share_file_list:
|
||||
if share_file["dir"]:
|
||||
# 顺序命名模式下,未设置update_subdir时不处理文件夹
|
||||
@ -1819,9 +1972,15 @@ class Quark:
|
||||
# 文件ID已存在于记录中,跳过处理
|
||||
continue
|
||||
|
||||
# 指定文件开始订阅/到达指定文件(含)结束历遍
|
||||
if share_file["fid"] == task.get("startfid", ""):
|
||||
break
|
||||
# 改进的起始文件过滤逻辑
|
||||
if start_fid:
|
||||
if share_file["fid"] == start_fid:
|
||||
start_file_found = True
|
||||
break # 找到起始文件,停止遍历
|
||||
# 如果还没找到起始文件,继续添加到转存列表
|
||||
else:
|
||||
# 没有设置起始文件,处理所有文件
|
||||
pass
|
||||
|
||||
file_size = share_file.get("size", 0)
|
||||
file_ext = os.path.splitext(share_file["file_name"])[1].lower()
|
||||
@ -1893,9 +2052,7 @@ class Quark:
|
||||
# print(f"跳过已存在的文件: {save_name}")
|
||||
pass
|
||||
|
||||
# 指定文件开始订阅/到达指定文件(含)结束历遍
|
||||
if share_file["fid"] == task.get("startfid", ""):
|
||||
break
|
||||
# 这里不需要再次检查起始文件,因为在前面的过滤中已经处理了
|
||||
|
||||
# 处理子文件夹
|
||||
for share_file in share_file_list:
|
||||
@ -2021,6 +2178,20 @@ class Quark:
|
||||
"updated_at": update_time,
|
||||
})
|
||||
|
||||
# 应用起始文件过滤逻辑
|
||||
start_fid = task.get("startfid", "")
|
||||
if start_fid:
|
||||
# 找到起始文件的索引
|
||||
start_index = -1
|
||||
for i, share_file in enumerate(share_file_list):
|
||||
if share_file["fid"] == start_fid:
|
||||
start_index = i
|
||||
break
|
||||
|
||||
if start_index >= 0:
|
||||
# 只处理起始文件之前的文件(不包括起始文件本身)
|
||||
share_file_list = share_file_list[:start_index]
|
||||
|
||||
# 添加符合的
|
||||
for share_file in share_file_list:
|
||||
# 检查文件ID是否存在于转存记录中
|
||||
@ -2029,10 +2200,6 @@ class Quark:
|
||||
# 文件ID已存在于记录中,跳过处理
|
||||
continue
|
||||
|
||||
# 指定文件开始订阅/到达指定文件(含)结束历遍
|
||||
if share_file["fid"] == task.get("startfid", ""):
|
||||
break
|
||||
|
||||
# 检查文件是否已存在(通过大小和扩展名)- 新增的文件查重逻辑
|
||||
is_duplicate = False
|
||||
if not share_file["dir"]: # 文件夹不进行内容查重
|
||||
@ -2429,8 +2596,12 @@ class Quark:
|
||||
)
|
||||
|
||||
# 修复文件树显示问题 - 防止文件名重复重复显示
|
||||
# 如果save_name与original_name相似(如:一个是"你好,星期六 - 2025-04-05.mp4",另一个是"20250405期.mp4")
|
||||
# 则只显示save_name,避免重复
|
||||
# 对于顺序命名和剧集命名模式,转存时使用原文件名显示
|
||||
# 因为实际文件是以原名保存的,重命名在后续步骤进行
|
||||
if task.get("use_sequence_naming") or task.get("use_episode_naming"):
|
||||
display_name = item['file_name'] # 使用原文件名
|
||||
else:
|
||||
# 其他模式使用save_name
|
||||
display_name = item['save_name']
|
||||
|
||||
# 确保只显示文件/文件夹名,而不是完整路径
|
||||
@ -2445,22 +2616,24 @@ class Quark:
|
||||
# 检查节点是否已存在于树中,避免重复添加
|
||||
if not tree.contains(item["fid"]):
|
||||
tree.create_node(
|
||||
f"{icon}{display_name}",
|
||||
display_name, # 只存储文件名,不包含图标
|
||||
item["fid"],
|
||||
parent=pdir_fid,
|
||||
data={
|
||||
"fid": f"{query_task_return['data']['save_as']['save_as_top_fids'][index]}",
|
||||
"path": f"{savepath}/{item['save_name']}",
|
||||
"is_dir": item["dir"],
|
||||
"icon": icon, # 将图标存储在data中
|
||||
},
|
||||
)
|
||||
|
||||
# 保存转存记录到数据库
|
||||
if not item["dir"]: # 只记录文件,不记录文件夹
|
||||
# 转存时先用原文件名记录,重命名后再更新
|
||||
self.create_transfer_record(
|
||||
task=task,
|
||||
file_info=item,
|
||||
renamed_to=item.get("save_name", item["file_name"])
|
||||
renamed_to=item["file_name"] # 转存时使用原文件名
|
||||
)
|
||||
|
||||
# 移除通知生成,由do_save函数统一处理
|
||||
@ -2500,10 +2673,17 @@ class Quark:
|
||||
non_dir_files = [f for f in dir_file_list if not f.get("dir", False)]
|
||||
is_empty_dir = len(non_dir_files) == 0
|
||||
|
||||
|
||||
|
||||
# 找出当前最大序号
|
||||
max_sequence = 0
|
||||
if not is_empty_dir: # 只有在目录非空时才寻找最大序号
|
||||
# 先检查目录中是否有符合命名规则的文件
|
||||
has_matching_files = False
|
||||
|
||||
for dir_file in dir_file_list:
|
||||
if dir_file.get("dir", False):
|
||||
continue # 跳过文件夹
|
||||
|
||||
if sequence_pattern == "{}":
|
||||
# 对于单独的{},直接尝试匹配整个文件名是否为数字
|
||||
file_name_without_ext = os.path.splitext(dir_file["file_name"])[0]
|
||||
@ -2513,15 +2693,58 @@ class Quark:
|
||||
try:
|
||||
current_seq = int(file_name_without_ext)
|
||||
max_sequence = max(max_sequence, current_seq)
|
||||
has_matching_files = True
|
||||
except (ValueError, IndexError):
|
||||
pass
|
||||
elif matches := re.match(regex_pattern, dir_file["file_name"]):
|
||||
try:
|
||||
current_seq = int(matches.group(1))
|
||||
max_sequence = max(max_sequence, current_seq)
|
||||
has_matching_files = True
|
||||
except (IndexError, ValueError):
|
||||
pass
|
||||
|
||||
if not has_matching_files:
|
||||
# 没有符合命名规则的文件时,检查数据库中的转存记录
|
||||
try:
|
||||
from app.sdk.db import RecordDB
|
||||
db = RecordDB()
|
||||
|
||||
# 获取当前保存路径
|
||||
current_save_path = task.get("savepath", "")
|
||||
if subdir_path:
|
||||
current_save_path = f"{current_save_path}/{subdir_path}"
|
||||
|
||||
# 查询该目录的转存记录
|
||||
records = db.get_records_by_save_path(current_save_path, include_subpaths=False)
|
||||
|
||||
# 从转存记录中提取最大序号
|
||||
max_sequence_from_records = 0
|
||||
for record in records:
|
||||
renamed_to = record.get("renamed_to", "")
|
||||
if renamed_to:
|
||||
if sequence_pattern == "{}":
|
||||
# 对于单独的{},直接尝试匹配整个文件名是否为数字
|
||||
file_name_without_ext = os.path.splitext(renamed_to)[0]
|
||||
if file_name_without_ext.isdigit():
|
||||
try:
|
||||
seq_num = int(file_name_without_ext)
|
||||
max_sequence_from_records = max(max_sequence_from_records, seq_num)
|
||||
except (ValueError, IndexError):
|
||||
pass
|
||||
elif matches := re.match(regex_pattern, renamed_to):
|
||||
try:
|
||||
seq_num = int(matches.group(1))
|
||||
max_sequence_from_records = max(max_sequence_from_records, seq_num)
|
||||
except (ValueError, IndexError):
|
||||
pass
|
||||
|
||||
# 使用记录中的最大序号
|
||||
max_sequence = max_sequence_from_records
|
||||
db.close()
|
||||
except Exception as e:
|
||||
max_sequence = 0
|
||||
|
||||
# 实现高级排序算法
|
||||
def extract_sorting_value(file):
|
||||
# 使用全局排序函数
|
||||
@ -2611,12 +2834,11 @@ class Quark:
|
||||
is_rename_count += 1
|
||||
|
||||
# 更新重命名记录到数据库(只更新renamed_to字段)
|
||||
# 不在这里直接调用update_transfer_record,而是在do_save中统一处理
|
||||
# self.update_transfer_record(
|
||||
# task=task,
|
||||
# file_info=dir_file,
|
||||
# renamed_to=save_name
|
||||
# )
|
||||
self.update_transfer_record(
|
||||
task=task,
|
||||
file_info=dir_file,
|
||||
renamed_to=save_name
|
||||
)
|
||||
else:
|
||||
error_msg = rename_return.get("message", "未知错误")
|
||||
rename_log = f"重命名: {dir_file['file_name']} → {save_name} 失败,{error_msg}"
|
||||
@ -2719,8 +2941,15 @@ class Quark:
|
||||
print("分享为空,文件已被分享者删除")
|
||||
return False, []
|
||||
|
||||
# 在剧集命名模式中,需要先对文件列表进行排序,然后再应用起始文件过滤
|
||||
# 使用全局排序函数进行排序(倒序,最新的在前)
|
||||
share_file_list = sorted(share_file_list, key=sort_file_by_name, reverse=True)
|
||||
|
||||
# 预先过滤分享文件列表,去除已存在的文件
|
||||
filtered_share_files = []
|
||||
start_fid = task.get("startfid", "")
|
||||
start_file_found = False
|
||||
|
||||
for share_file in share_file_list:
|
||||
if share_file["dir"]:
|
||||
# 处理子目录
|
||||
@ -2734,9 +2963,15 @@ class Quark:
|
||||
# 文件ID已存在于记录中,跳过处理
|
||||
continue
|
||||
|
||||
# 指定文件开始订阅/到达指定文件(含)结束历遍
|
||||
if share_file["fid"] == task.get("startfid", ""):
|
||||
break
|
||||
# 改进的起始文件过滤逻辑
|
||||
if start_fid:
|
||||
if share_file["fid"] == start_fid:
|
||||
start_file_found = True
|
||||
break # 找到起始文件,停止遍历
|
||||
# 如果还没找到起始文件,继续添加到转存列表
|
||||
else:
|
||||
# 没有设置起始文件,处理所有文件
|
||||
pass
|
||||
|
||||
# 从共享文件中提取剧集号
|
||||
episode_num = extract_episode_number_local(share_file["file_name"])
|
||||
@ -2901,10 +3136,11 @@ class Quark:
|
||||
# 保存转存记录到数据库
|
||||
for saved_item in need_save_list:
|
||||
if not saved_item.get("dir", False): # 只记录文件,不记录文件夹
|
||||
# 转存时先用原文件名记录,重命名后再更新
|
||||
self.create_transfer_record(
|
||||
task=task,
|
||||
file_info=saved_item,
|
||||
renamed_to=saved_item.get("save_name", saved_item["file_name"])
|
||||
renamed_to=saved_item["file_name"] # 转存时使用原文件名
|
||||
)
|
||||
|
||||
# 刷新目录列表以获取新保存的文件
|
||||
@ -3473,9 +3709,9 @@ def do_save(account, tasklist=[]):
|
||||
# 完全替换日志,只显示成功部分
|
||||
rename_logs = success_logs
|
||||
|
||||
# 只有当is_new_tree为False且有成功的重命名日志时,才需要创建新的Tree对象
|
||||
# 这确保只显示当次转存的文件,而不是目录中的所有文件
|
||||
if task.get("shareurl") and (not is_new_tree or is_new_tree is False) and rename_logs and is_rename:
|
||||
# 对于顺序命名模式,需要重新创建文件树以显示实际的文件名
|
||||
# 这确保显示的是实际存在的文件名,而不是预期的文件名
|
||||
if task.get("shareurl") and rename_logs and is_rename and (task.get("use_sequence_naming") or task.get("use_episode_naming")):
|
||||
# 获取当前目录
|
||||
savepath = re.sub(r"/{2,}", "/", f"/{task['savepath']}")
|
||||
if account.savepath_fid.get(savepath):
|
||||
@ -3490,26 +3726,43 @@ def do_save(account, tasklist=[]):
|
||||
},
|
||||
)
|
||||
|
||||
# 从重命名日志中提取新文件名
|
||||
renamed_files = {}
|
||||
# 获取实际的文件名映射
|
||||
actual_file_names = account.get_actual_file_names_from_directory(task, rename_logs)
|
||||
|
||||
# 从重命名日志中提取预期的文件名映射
|
||||
expected_renamed_files = {}
|
||||
for log in rename_logs:
|
||||
# 格式:重命名: 旧名 → 新名
|
||||
match = re.search(r'重命名: (.*?) → (.+?)($|\s|,|失败)', log)
|
||||
if match:
|
||||
old_name = match.group(1).strip()
|
||||
new_name = match.group(2).strip()
|
||||
renamed_files[old_name] = new_name
|
||||
expected_new_name = match.group(2).strip()
|
||||
expected_renamed_files[old_name] = expected_new_name
|
||||
|
||||
# 获取文件列表,只添加重命名的文件
|
||||
fresh_dir_file_list = account.ls_dir(account.savepath_fid[savepath])
|
||||
|
||||
# 添加重命名后的文件到树中
|
||||
# 添加实际存在的文件到树中
|
||||
for file in fresh_dir_file_list:
|
||||
if not file["dir"]: # 只处理文件
|
||||
# 只添加重命名后的文件(当次转存的文件)
|
||||
if file["file_name"] in renamed_files.values():
|
||||
# 检查这个文件是否是当次转存的文件
|
||||
is_transferred_file = False
|
||||
|
||||
# 方法1:检查文件名是否在实际重命名结果中
|
||||
if file["file_name"] in actual_file_names.values():
|
||||
is_transferred_file = True
|
||||
|
||||
# 方法2:检查文件名是否在预期重命名结果中(用于重命名失败的情况)
|
||||
elif file["file_name"] in expected_renamed_files.values():
|
||||
is_transferred_file = True
|
||||
|
||||
# 方法3:检查文件名是否是原始文件名(重命名失败保持原名)
|
||||
elif file["file_name"] in expected_renamed_files.keys():
|
||||
is_transferred_file = True
|
||||
|
||||
if is_transferred_file:
|
||||
new_tree.create_node(
|
||||
file["file_name"],
|
||||
file["file_name"], # 使用实际存在的文件名
|
||||
file["fid"],
|
||||
parent="root",
|
||||
data={
|
||||
@ -3559,30 +3812,33 @@ def do_save(account, tasklist=[]):
|
||||
|
||||
# 按文件名排序
|
||||
if is_special_sequence:
|
||||
# 对于顺序命名模式,使用重命名日志来获取新增的文件
|
||||
# 对于顺序命名模式,直接使用文件树中的实际文件名
|
||||
if rename_logs:
|
||||
# 从重命名日志提取新旧文件名
|
||||
renamed_files = {}
|
||||
for log in rename_logs:
|
||||
# 格式:重命名: 旧名 → 新名
|
||||
match = re.search(r'重命名: (.*?) → (.+?)($|\s|,|失败)', log)
|
||||
# 直接显示文件树中的实际文件名(已经是重命名后的结果)
|
||||
# 按照文件名中的序号进行排序
|
||||
def extract_sequence_number(node):
|
||||
filename = remove_file_icons(node.tag)
|
||||
# 尝试从文件名中提取序号,如 "乘风2025 - S06E01.flac" -> 1
|
||||
import re
|
||||
match = re.search(r'E(\d+)', filename)
|
||||
if match:
|
||||
old_name = match.group(1).strip()
|
||||
new_name = match.group(2).strip()
|
||||
renamed_files[old_name] = new_name
|
||||
return int(match.group(1))
|
||||
# 如果没有找到E序号,尝试其他模式
|
||||
match = re.search(r'(\d+)', filename)
|
||||
if match:
|
||||
return int(match.group(1))
|
||||
return 0
|
||||
|
||||
# 只显示重命名的文件
|
||||
for node in file_nodes:
|
||||
# 获取原始文件名(去除已有图标)
|
||||
orig_filename = node.tag.lstrip("🎞️")
|
||||
# 检查此文件是否在重命名日志中
|
||||
if orig_filename in renamed_files:
|
||||
# 使用重命名后的文件名
|
||||
new_filename = renamed_files[orig_filename]
|
||||
# 按序号排序
|
||||
sorted_nodes = sorted(file_nodes, key=extract_sequence_number)
|
||||
|
||||
for node in sorted_nodes:
|
||||
# 获取实际文件名(去除已有图标)
|
||||
actual_filename = remove_file_icons(node.tag)
|
||||
# 获取适当的图标
|
||||
icon = get_file_icon(new_filename, is_dir=node.data.get("is_dir", False))
|
||||
icon = get_file_icon(actual_filename, is_dir=node.data.get("is_dir", False))
|
||||
# 添加到显示列表
|
||||
display_files.append((f"{icon} {new_filename}", node))
|
||||
display_files.append((f"{icon} {actual_filename}", node))
|
||||
else:
|
||||
# 如果没有重命名日志,使用原来的顺序命名逻辑
|
||||
if task.get("use_sequence_naming") and task.get("sequence_naming"):
|
||||
@ -3594,7 +3850,7 @@ def do_save(account, tasklist=[]):
|
||||
# 提取序号(从1开始)
|
||||
file_num = i + 1
|
||||
# 获取原始文件的扩展名
|
||||
orig_filename = node.tag.lstrip("🎞️")
|
||||
orig_filename = remove_file_icons(node.tag)
|
||||
file_ext = os.path.splitext(orig_filename)[1]
|
||||
# 生成新的文件名(使用顺序命名模式)
|
||||
if sequence_pattern == "{}":
|
||||
@ -3608,7 +3864,7 @@ def do_save(account, tasklist=[]):
|
||||
display_files.append((f"{icon} {new_filename}", node))
|
||||
|
||||
# 按数字排序
|
||||
display_files.sort(key=lambda x: int(os.path.splitext(x[0].lstrip("🎞️"))[0]) if os.path.splitext(x[0].lstrip("🎞️"))[0].isdigit() else float('inf'))
|
||||
display_files.sort(key=lambda x: int(os.path.splitext(remove_file_icons(x[0]))[0]) if os.path.splitext(remove_file_icons(x[0]))[0].isdigit() else float('inf'))
|
||||
# 对于剧集命名模式
|
||||
elif task.get("use_episode_naming") and task.get("episode_naming"):
|
||||
# 从重命名日志提取新旧文件名 (备用)
|
||||
@ -3632,7 +3888,7 @@ def do_save(account, tasklist=[]):
|
||||
# 只显示重命名的文件
|
||||
for node in file_nodes:
|
||||
# 获取原始文件名(去除已有图标)
|
||||
orig_filename = node.tag.lstrip("🎞️")
|
||||
orig_filename = remove_file_icons(node.tag)
|
||||
# 检查此文件是否在重命名日志中
|
||||
if orig_filename in renamed_files:
|
||||
# 使用重命名后的文件名
|
||||
@ -3646,9 +3902,9 @@ def do_save(account, tasklist=[]):
|
||||
if not display_files:
|
||||
for node in sorted(file_nodes, key=lambda node: node.tag):
|
||||
# 获取原始文件名(去除已有图标)
|
||||
orig_filename = node.tag.lstrip("🎞️")
|
||||
# 添加适当的图标
|
||||
icon = get_file_icon(orig_filename, is_dir=node.data.get("is_dir", False))
|
||||
orig_filename = remove_file_icons(node.tag)
|
||||
# 优先使用存储在data中的图标,否则重新计算
|
||||
icon = node.data.get("icon") if node.data else get_file_icon(orig_filename, is_dir=node.data.get("is_dir", False) if node.data else False)
|
||||
display_files.append((f"{icon} {orig_filename}", node))
|
||||
else:
|
||||
# 其他模式:显示原始文件名
|
||||
@ -3759,7 +4015,7 @@ def do_save(account, tasklist=[]):
|
||||
|
||||
for node in sorted(all_file_nodes, key=lambda node: node.tag):
|
||||
# 获取原始文件名(去除已有图标)
|
||||
orig_filename = node.tag.lstrip("🎞️")
|
||||
orig_filename = remove_file_icons(node.tag)
|
||||
# 添加适当的图标
|
||||
icon = get_file_icon(orig_filename, is_dir=False)
|
||||
|
||||
@ -3872,7 +4128,7 @@ def do_save(account, tasklist=[]):
|
||||
|
||||
# 排序函数,使用文件节点作为输入
|
||||
def sort_nodes(nodes):
|
||||
return sorted(nodes, key=lambda node: sort_file_by_name(node.tag.lstrip("🎞️")))
|
||||
return sorted(nodes, key=lambda node: sort_file_by_name(remove_file_icons(node.tag)))
|
||||
|
||||
# 初始化最终显示文件的字典
|
||||
final_display_files = {
|
||||
@ -3905,7 +4161,7 @@ def do_save(account, tasklist=[]):
|
||||
# 检查根目录是否有新增文件
|
||||
root_new_files = []
|
||||
for node in files_by_dir["root"]:
|
||||
file_name = node.tag.lstrip("🎞️")
|
||||
file_name = remove_file_icons(node.tag)
|
||||
# 判断是否为新增文件
|
||||
is_new_file = False
|
||||
|
||||
@ -3954,7 +4210,7 @@ def do_save(account, tasklist=[]):
|
||||
|
||||
# 检查该目录下的文件是否是新文件
|
||||
for file_node in dir_files:
|
||||
file_name = file_node.tag.lstrip("🎞️")
|
||||
file_name = remove_file_icons(file_node.tag)
|
||||
# 判断是否为新增文件
|
||||
is_new_file = False
|
||||
|
||||
@ -4223,7 +4479,7 @@ def do_save(account, tasklist=[]):
|
||||
|
||||
# 显示文件
|
||||
file_prefix = prefix + ("└── " if is_file_last else "├── ")
|
||||
file_name = file_node.tag.lstrip("🎞️")
|
||||
file_name = remove_file_icons(file_node.tag)
|
||||
icon = get_file_icon(file_name, is_dir=False)
|
||||
add_notify(format_file_display(file_prefix, icon, file_name))
|
||||
|
||||
@ -4249,8 +4505,11 @@ def do_save(account, tasklist=[]):
|
||||
# 过滤出非目录的文件
|
||||
file_nodes = [f for f in dir_file_list if not f["dir"]]
|
||||
|
||||
# 从重命名日志提取新旧文件名
|
||||
renamed_files = {}
|
||||
# 获取实际的文件名映射
|
||||
actual_file_names = account.get_actual_file_names_from_directory(task, rename_logs)
|
||||
|
||||
# 从重命名日志提取预期的新旧文件名映射
|
||||
expected_renamed_files = {}
|
||||
for log in rename_logs:
|
||||
# 格式:重命名: 旧名 → 新名
|
||||
if "重命名:" in log and " → " in log:
|
||||
@ -4258,22 +4517,27 @@ def do_save(account, tasklist=[]):
|
||||
parts = log.split("重命名:", 1)[1].strip()
|
||||
# 再按箭头分割
|
||||
if " → " in parts:
|
||||
old_name, new_name = parts.split(" → ", 1)
|
||||
old_name, expected_new_name = parts.split(" → ", 1)
|
||||
# 只处理失败信息,不截断正常文件名
|
||||
if " 失败," in new_name:
|
||||
new_name = new_name.split(" 失败,")[0]
|
||||
if " 失败," in expected_new_name:
|
||||
expected_new_name = expected_new_name.split(" 失败,")[0]
|
||||
# 去除首尾空格
|
||||
old_name = old_name.strip()
|
||||
new_name = new_name.strip()
|
||||
renamed_files[old_name] = new_name
|
||||
expected_new_name = expected_new_name.strip()
|
||||
expected_renamed_files[old_name] = expected_new_name
|
||||
|
||||
# 确保至少显示重命名后的文件
|
||||
# 确保至少显示实际存在的文件
|
||||
display_files = []
|
||||
|
||||
# 添加所有重命名的目标文件
|
||||
for old_name, new_name in renamed_files.items():
|
||||
if new_name not in display_files:
|
||||
display_files.append(new_name)
|
||||
# 添加所有实际存在的转存文件
|
||||
for old_name in expected_renamed_files.keys():
|
||||
# 获取实际的文件名
|
||||
actual_name = actual_file_names.get(old_name, expected_renamed_files[old_name])
|
||||
|
||||
# 检查文件是否实际存在于目录中
|
||||
if any(f["file_name"] == actual_name for f in file_nodes):
|
||||
if actual_name not in display_files:
|
||||
display_files.append(actual_name)
|
||||
|
||||
# 此外,检查是否有新的文件节点(比较节点时间)
|
||||
if not display_files and is_new_tree and hasattr(is_new_tree, 'nodes'):
|
||||
@ -4444,21 +4708,29 @@ def do_save(account, tasklist=[]):
|
||||
# 过滤出非目录的文件
|
||||
file_nodes = [f for f in dir_file_list if not f["dir"]]
|
||||
|
||||
# 从重命名日志提取新旧文件名
|
||||
renamed_files = {}
|
||||
# 获取实际的文件名映射
|
||||
actual_file_names = account.get_actual_file_names_from_directory(task, rename_logs)
|
||||
|
||||
# 从重命名日志提取预期的新旧文件名映射
|
||||
expected_renamed_files = {}
|
||||
for log in rename_logs:
|
||||
# 格式:重命名: 旧名 → 新名
|
||||
match = re.search(r'重命名: (.*?) → (.+?)($|\s|,|失败)', log)
|
||||
if match:
|
||||
old_name = match.group(1).strip()
|
||||
new_name = match.group(2).strip()
|
||||
renamed_files[old_name] = new_name
|
||||
expected_new_name = match.group(2).strip()
|
||||
expected_renamed_files[old_name] = expected_new_name
|
||||
|
||||
# 只显示重命名的文件
|
||||
# 只显示实际存在的转存文件
|
||||
display_files = []
|
||||
for file in file_nodes:
|
||||
if file["file_name"] in renamed_files:
|
||||
display_files.append(renamed_files[file["file_name"]])
|
||||
for old_name in expected_renamed_files.keys():
|
||||
# 获取实际的文件名
|
||||
actual_name = actual_file_names.get(old_name, expected_renamed_files[old_name])
|
||||
|
||||
# 检查文件是否实际存在于目录中
|
||||
if any(f["file_name"] == actual_name for f in file_nodes):
|
||||
if actual_name not in display_files:
|
||||
display_files.append(actual_name)
|
||||
|
||||
# 如果没有找到任何文件要显示,使用原始逻辑
|
||||
if not display_files:
|
||||
|
||||
@ -2,3 +2,4 @@ flask
|
||||
apscheduler
|
||||
requests
|
||||
treelib
|
||||
pypinyin
|
||||
|
||||
Loading…
Reference in New Issue
Block a user