mirror of
https://github.com/Cp0204/quark-auto-save.git
synced 2026-01-18 19:00:44 +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 和豆瓣搜索功能、页面视图切换功能、账号设置功能等等。
|
- **WebUI**:对整个 WebUI 进行了重塑,增加了更多实用功能,如文件选择和预览界面的排序功能、资源搜索的过滤功能、TMDB 和豆瓣搜索功能、页面视图切换功能、账号设置功能等等。
|
||||||
- **查重逻辑**:支持优先通过历史转存记录查重,对于有转存记录的文件,即使删除网盘文件,也不会重复转存。
|
- **查重逻辑**:支持优先通过历史转存记录查重,对于有转存记录的文件,即使删除网盘文件,也不会重复转存。
|
||||||
- **Aria2**:支持成功添加 Aria2 下载任务后自动删除夸克网盘内对应的文件,清理网盘空间。
|
- **Aria2**:支持成功添加 Aria2 下载任务后自动删除夸克网盘内对应的文件,清理网盘空间。
|
||||||
- **文件整理**:支持浏览和管理多个夸克账号的网盘文件,支持批量重命名(支持应用完整的命名、过滤规则和撤销重命名等操作)、删除文件等操作。
|
- **文件整理**:支持浏览和管理多个夸克账号的网盘文件,支持单项/批量重命名(支持应用完整的命名、过滤规则和撤销重命名等操作)、移动文件、删除文件、新建文件夹等操作。
|
||||||
|
|
||||||
本项目修改后的版本为个人需求定制版,目的是满足我自己的使用需求,某些(我不用的)功能可能会因为修改而出现 BUG,不一定会被修复。若你要使用本项目,请知晓本人不是程序员,我无法保证本项目的稳定性,如果你在使用过程中发现了 BUG,可以在 Issues 中提交,但不保证每个 BUG 都能被修复,请谨慎使用,风险自担。
|
本项目修改后的版本为个人需求定制版,目的是满足我自己的使用需求,某些(我不用的)功能可能会因为修改而出现 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__))))
|
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
|
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:
|
try:
|
||||||
# 先尝试相对导入
|
# 先尝试相对导入
|
||||||
@ -859,6 +867,8 @@ def get_share_detail():
|
|||||||
|
|
||||||
if episode_num is not None:
|
if episode_num is not None:
|
||||||
file["file_name_re"] = episode_pattern.replace("[]", f"{episode_num:02d}") + extension
|
file["file_name_re"] = episode_pattern.replace("[]", f"{episode_num:02d}") + extension
|
||||||
|
# 添加episode_number字段用于前端排序
|
||||||
|
file["episode_number"] = episode_num
|
||||||
else:
|
else:
|
||||||
# 没有提取到集号,显示无法识别的提示
|
# 没有提取到集号,显示无法识别的提示
|
||||||
file["file_name_re"] = "× 无法识别剧集编号"
|
file["file_name_re"] = "× 无法识别剧集编号"
|
||||||
@ -1011,6 +1021,114 @@ def delete_file():
|
|||||||
return jsonify(response)
|
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"])
|
@app.route("/api/add_task", methods=["POST"])
|
||||||
def add_task():
|
def add_task():
|
||||||
@ -1581,9 +1699,11 @@ def get_file_list():
|
|||||||
# 优化排序:使用更高效的排序方法
|
# 优化排序:使用更高效的排序方法
|
||||||
def get_sort_key(file_item):
|
def get_sort_key(file_item):
|
||||||
if sort_by == "file_name":
|
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":
|
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
|
else: # updated_at
|
||||||
return file_item["updated_at"]
|
return file_item["updated_at"]
|
||||||
|
|
||||||
@ -1738,7 +1858,8 @@ def preview_rename():
|
|||||||
preview_results.append({
|
preview_results.append({
|
||||||
"original_name": file["file_name"],
|
"original_name": file["file_name"],
|
||||||
"new_name": new_name,
|
"new_name": new_name,
|
||||||
"file_id": file["fid"]
|
"file_id": file["fid"],
|
||||||
|
"episode_number": episode_num # 添加集数字段用于前端排序
|
||||||
})
|
})
|
||||||
else:
|
else:
|
||||||
# 没有提取到集号,显示无法识别的提示
|
# 没有提取到集号,显示无法识别的提示
|
||||||
|
|||||||
@ -174,9 +174,34 @@ class RecordDB:
|
|||||||
total_records = cursor.fetchone()[0]
|
total_records = cursor.fetchone()[0]
|
||||||
|
|
||||||
# 获取分页数据
|
# 获取分页数据
|
||||||
query_sql = f"SELECT * FROM transfer_records {where_sql} ORDER BY {sort_by} {order_direction} LIMIT ? OFFSET ?"
|
if sort_by in ["task_name", "original_name", "renamed_to"]:
|
||||||
cursor.execute(query_sql, params + [page_size, offset])
|
# 对于需要拼音排序的字段,先获取所有数据然后在Python中进行拼音排序
|
||||||
records = cursor.fetchall()
|
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()
|
||||||
|
|
||||||
# 将结果转换为字典列表
|
# 将结果转换为字典列表
|
||||||
columns = [col[0] for col in cursor.description]
|
columns = [col[0] for col in cursor.description]
|
||||||
|
|||||||
@ -804,12 +804,16 @@ main div[v-if="activeTab === 'config'"] .row.title:first-child {
|
|||||||
/* 文件夹图标样式 */
|
/* 文件夹图标样式 */
|
||||||
.bi-folder {
|
.bi-folder {
|
||||||
color: var(--dark-text-color);
|
color: var(--dark-text-color);
|
||||||
font-size: 0.98rem;
|
font-size: 1.01rem;
|
||||||
|
position: relative;
|
||||||
|
top: 0.5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 重置文件夹图标样式 */
|
/* 重置文件夹图标样式 */
|
||||||
.bi-folder-x {
|
.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 {
|
.bi-link-45deg {
|
||||||
color: var(--dark-text-color);
|
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 {
|
.sidebar .nav-link .bi-list-ul {
|
||||||
font-size: 1.08rem;
|
font-size: 1.09rem;
|
||||||
position: relative;
|
position: relative;
|
||||||
top: 0.5px; /* 向下微调 */
|
top: 0.5px; /* 向下微调 */
|
||||||
}
|
}
|
||||||
@ -4187,6 +4193,11 @@ select.task-filter-select,
|
|||||||
color: var(--focus-border-color);
|
color: var(--focus-border-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 文件整理页面面包屑导航当前目录样式 */
|
||||||
|
.file-manager-breadcrumb .breadcrumb-item .text-muted {
|
||||||
|
color: var(--dark-text-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
/* 隐藏Bootstrap默认的面包屑分隔符 */
|
/* 隐藏Bootstrap默认的面包屑分隔符 */
|
||||||
.file-manager-breadcrumb .breadcrumb-item + .breadcrumb-item::before {
|
.file-manager-breadcrumb .breadcrumb-item + .breadcrumb-item::before {
|
||||||
color: var(--dark-text-color);
|
color: var(--dark-text-color);
|
||||||
@ -4244,6 +4255,13 @@ select.task-filter-select,
|
|||||||
font-size: 1.17rem;
|
font-size: 1.17rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 文件整理页面新建文件夹按钮图标大小 */
|
||||||
|
.batch-rename-btn .bi-folder-plus {
|
||||||
|
font-size: 1.01rem;
|
||||||
|
position: relative;
|
||||||
|
top: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
/* 确保文件整理页面的Plex和AList按钮样式与任务列表一致 */
|
/* 确保文件整理页面的Plex和AList按钮样式与任务列表一致 */
|
||||||
.batch-rename-btn.btn-outline-plex,
|
.batch-rename-btn.btn-outline-plex,
|
||||||
.batch-rename-btn.btn-outline-alist {
|
.batch-rename-btn.btn-outline-alist {
|
||||||
@ -4288,6 +4306,8 @@ select.task-filter-select,
|
|||||||
filter: brightness(0) invert(1);
|
filter: brightness(0) invert(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* 文件表格中的展开按钮 */
|
/* 文件表格中的展开按钮 */
|
||||||
.expand-button {
|
.expand-button {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@ -4582,6 +4602,11 @@ table.selectable-files .rename-record-btn:hover {
|
|||||||
color: #0A42CC !important;
|
color: #0A42CC !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 文件整理页面移动按钮悬停效果 */
|
||||||
|
table.selectable-files .move-record-btn:hover {
|
||||||
|
color: #0A42CC !important;
|
||||||
|
}
|
||||||
|
|
||||||
/* 文件整理页面删除按钮悬停效果 */
|
/* 文件整理页面删除按钮悬停效果 */
|
||||||
table.selectable-files .delete-record-btn:hover {
|
table.selectable-files .delete-record-btn:hover {
|
||||||
color: #b02a37 !important;
|
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,
|
tr:has(.expanded-text) .file-size-cell .rename-record-btn,
|
||||||
.selectable-records tbody 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,保持默认的隐藏状态 */
|
/* 不设置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 {
|
.delete-record-btn {
|
||||||
color: #dc3545;
|
color: #dc3545;
|
||||||
@ -4803,11 +4839,34 @@ tr.selected-record .file-size-cell .delete-record-btn,
|
|||||||
|
|
||||||
/* 文件整理页面的重命名文件按钮图标大小 */
|
/* 文件整理页面的重命名文件按钮图标大小 */
|
||||||
.selectable-files .rename-record-btn .bi-pencil {
|
.selectable-files .rename-record-btn .bi-pencil {
|
||||||
font-size: 0.98rem;
|
font-size: 0.99rem;
|
||||||
position: relative;
|
position: relative;
|
||||||
left: 0.5px;
|
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 {
|
.selectable-files .delete-record-btn {
|
||||||
color: #dc3545;
|
color: #dc3545;
|
||||||
@ -4820,7 +4879,7 @@ tr.selected-record .file-size-cell .delete-record-btn,
|
|||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: auto; /* 移除右对齐 */
|
right: auto; /* 移除右对齐 */
|
||||||
left: 32px; /* 在重命名按钮右侧,调整间距 */
|
left: 59px; /* 在移动按钮右侧,调整间距 */
|
||||||
top: 50%;
|
top: 50%;
|
||||||
transform: translateY(-50%);
|
transform: translateY(-50%);
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
@ -4832,9 +4891,11 @@ tr.selected-record .file-size-cell .delete-record-btn,
|
|||||||
min-width: 80px;
|
min-width: 80px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 修复:确保在悬停和选中状态下重命名和删除按钮显示 */
|
/* 修复:确保在悬停和选中状态下重命名、移动和删除按钮显示 */
|
||||||
.selectable-files tr:hover .file-size-cell .rename-record-btn,
|
.selectable-files tr:hover .file-size-cell .rename-record-btn,
|
||||||
.selectable-files tr.selected-file .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:hover .file-size-cell .delete-record-btn,
|
||||||
.selectable-files tr.selected-file .file-size-cell .delete-record-btn {
|
.selectable-files tr.selected-file .file-size-cell .delete-record-btn {
|
||||||
display: inline-flex !important;
|
display: inline-flex !important;
|
||||||
@ -4887,6 +4948,23 @@ body .selectable-files tr.selected-file .file-size-cell .rename-record-btn {
|
|||||||
opacity: 1 !important;
|
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 tbody tr:hover .file-size-cell .delete-record-btn,
|
||||||
body .selectable-files tr.selected-file .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;
|
position: absolute !important;
|
||||||
top: 50% !important;
|
top: 50% !important;
|
||||||
transform: translateY(-50%) !important;
|
transform: translateY(-50%) !important;
|
||||||
left: 32px !important;
|
left: 59px !important;
|
||||||
width: 24px !important;
|
width: 24px !important;
|
||||||
height: 24px !important;
|
height: 24px !important;
|
||||||
align-items: center !important;
|
align-items: center !important;
|
||||||
@ -4921,6 +4999,23 @@ body .selectable-files tr.selected-file:has([style*="white-space: normal"]) .fil
|
|||||||
opacity: 1 !important;
|
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 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 {
|
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;
|
position: absolute !important;
|
||||||
top: 8px !important;
|
top: 8px !important;
|
||||||
transform: none !important;
|
transform: none !important;
|
||||||
left: 32px !important;
|
left: 59px !important;
|
||||||
width: 24px !important;
|
width: 24px !important;
|
||||||
height: 24px !important;
|
height: 24px !important;
|
||||||
align-items: center !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;
|
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) {
|
@media (max-width: 767.98px) {
|
||||||
.file-manager-rule-bar-responsive {
|
.file-manager-rule-bar-responsive {
|
||||||
display: flex;
|
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 file_name_without_ext = filename.replace(/\.[^/.]+$/, '');
|
||||||
let date_value = Infinity, episode_value = Infinity, segment_value = 0;
|
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. 日期提取
|
// 1. 日期提取
|
||||||
let match;
|
let match;
|
||||||
// YYYY-MM-DD
|
// YYYY-MM-DD
|
||||||
@ -142,7 +155,7 @@ function sortFileByName(file) {
|
|||||||
else if (/[中][集期话部篇]?|[集期话部篇]中/.test(filename)) segment_value = 2;
|
else if (/[中][集期话部篇]?|[集期话部篇]中/.test(filename)) segment_value = 2;
|
||||||
else if (/[下][集期话部篇]?|[集期话部篇]下/.test(filename)) segment_value = 3;
|
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/axios.min.js"></script>
|
||||||
<script src="./static/js/v-jsoneditor.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/sort_file_by_name.js"></script>
|
||||||
|
<script src="./static/js/pinyin-pro.min.js"></script>
|
||||||
<script>
|
<script>
|
||||||
// 添加检测文本溢出的自定义指令
|
// 添加检测文本溢出的自定义指令
|
||||||
Vue.directive('check-overflow', {
|
Vue.directive('check-overflow', {
|
||||||
@ -748,7 +749,7 @@
|
|||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input type="text" name="shareurl[]" class="form-control" v-model="task.shareurl" placeholder="必填" @blur="changeShareurl(task)">
|
<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">
|
<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">
|
<div class="input-group-text">
|
||||||
<a target="_blank" :href="task.shareurl"><i class="bi bi-link-45deg"></i></a>
|
<a target="_blank" :href="task.shareurl"><i class="bi bi-link-45deg"></i></a>
|
||||||
</div>
|
</div>
|
||||||
@ -763,7 +764,7 @@
|
|||||||
<input type="text" name="savepath[]" class="form-control" v-model="task.savepath" placeholder="必填" @focus="focusTaskname(index, task)">
|
<input type="text" name="savepath[]" class="form-control" v-model="task.savepath" placeholder="必填" @focus="focusTaskname(index, task)">
|
||||||
<div class="input-group-append">
|
<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" 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>
|
<button type="button" class="btn btn-outline-secondary" @click="resetFolder(index)" title="重置文件夹:此操作将删除当前保存路径中的所有文件及相关转存记录,且不可恢复,请谨慎操作"><i class="bi bi-folder-x"></i></button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -801,18 +802,18 @@
|
|||||||
</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>
|
<label class="col-sm-2 col-form-label">起始文件</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<div class="input-group">
|
<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">
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group row" title="匹配成功的文件夹的所有嵌套目录都会被更新,并且会应用与根目录相同的重命名和过滤规则。注意:原理是逐级索引,深层嵌套目录的场景下效率非常低,慎用.*">
|
<div class="form-group row" title="匹配成功的文件夹的所有嵌套目录都会被更新,并且会应用与根目录相同的正则命名和过滤规则。注意:原理是逐级索引,深层嵌套目录的场景下效率非常低,慎用.*">
|
||||||
<label class="col-sm-2 col-form-label">更新目录</label>
|
<label class="col-sm-2 col-form-label">更新目录</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<input type="text" name="update_subdir[]" class="form-control" v-model="task.update_subdir" placeholder="可选,输入需要更新的子目录的文件夹名称(或正则表达式),多个项目用竖线分隔,如:4K|1080P">
|
<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">
|
<div class="input-group">
|
||||||
<input type="date" name="enddate[]" class="form-control date-input-no-icon" v-model="task.enddate" placeholder="可选" :ref="'enddate_' + index">
|
<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">
|
<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>
|
<i class="bi bi-calendar3"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -1051,6 +1052,9 @@
|
|||||||
</div>
|
</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-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-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="刷新当前目录缓存">
|
<button type="button" class="btn btn-outline-primary batch-rename-btn" @click="refreshCurrentFolderCache" title="刷新当前目录缓存">
|
||||||
<i class="bi bi-arrow-clockwise"></i>
|
<i class="bi bi-arrow-clockwise"></i>
|
||||||
</button>
|
</button>
|
||||||
@ -1068,6 +1072,9 @@
|
|||||||
</div>
|
</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-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-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="刷新当前目录缓存">
|
<button type="button" class="btn btn-outline-primary batch-rename-btn" @click="refreshCurrentFolderCache" title="刷新当前目录缓存">
|
||||||
<i class="bi bi-arrow-clockwise"></i>
|
<i class="bi bi-arrow-clockwise"></i>
|
||||||
</button>
|
</button>
|
||||||
@ -1206,6 +1213,9 @@
|
|||||||
<span class="rename-record-btn" @click.stop="startRenameFile(file)" title="重命名文件">
|
<span class="rename-record-btn" @click.stop="startRenameFile(file)" title="重命名文件">
|
||||||
<i class="bi bi-pencil"></i>
|
<i class="bi bi-pencil"></i>
|
||||||
</span>
|
</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="删除文件">
|
<span class="delete-record-btn" @click.stop="deleteSelectedFilesForManager(file.fid, file.file_name, file.dir)" title="删除文件">
|
||||||
<i class="bi bi-trash3"></i>
|
<i class="bi bi-trash3"></i>
|
||||||
</span>
|
</span>
|
||||||
@ -1302,9 +1312,9 @@
|
|||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h5 class="modal-title" style="font-weight: 600; font-family: inherit; letter-spacing: normal;">
|
<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.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>
|
<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>
|
<div v-if="modalLoading" class="spinner-border spinner-border-sm m-1" role="status"></div>
|
||||||
</h5>
|
</h5>
|
||||||
@ -1328,13 +1338,13 @@
|
|||||||
<!-- 文件列表 -->
|
<!-- 文件列表 -->
|
||||||
<div class="mb-3" v-if="fileSelect.previewRegex">
|
<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 v-if="formData.tasklist[fileSelect.index].use_sequence_naming">
|
||||||
<div style="margin-bottom: 1px; padding-left: 12px; display: flex; align-items: center;">
|
<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>
|
<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>
|
||||||
<div style="padding-left: 12px; display: flex; align-items: center;">
|
<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>
|
</div>
|
||||||
<div v-else-if="formData.tasklist[fileSelect.index].use_episode_naming">
|
<div v-else-if="formData.tasklist[fileSelect.index].use_episode_naming">
|
||||||
@ -1385,7 +1395,7 @@
|
|||||||
<template v-if="!fileSelect.previewRegex">
|
<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-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-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>
|
</template>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</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-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-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-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)" style="cursor: pointer;">删除文件</a>
|
||||||
<a @click.stop.prevent="deleteSelectedFiles(file.fid, file.file_name, file.dir, true)" style="cursor: pointer; margin-left: 10px;">删除文件和记录</a>
|
<a @click.stop.prevent="deleteSelectedFiles(file.fid, file.file_name, file.dir, true)" style="cursor: pointer; margin-left: 10px;">删除文件和记录</a>
|
||||||
</td>
|
</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;">
|
<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>
|
共 {{ fileSelect.fileList.length }} 个项目<span v-if="fileSelect.selectedFiles.length > 0">,已选中 {{ fileSelect.selectedFiles.length }} 个项目</span>
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="btn btn-primary btn-sm" @click="selectCurrentFolder()">{{fileSelect.selectShare ? '转存当前文件夹' : '保存到当前文件夹'}}</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" 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-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>
|
||||||
<div class="modal-footer" v-if="fileSelect.previewRegex && fileSelect.index === -1">
|
<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;">
|
<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", // 默认排序顺序
|
sortOrder: "desc", // 默认排序顺序
|
||||||
selectedFiles: [], // 存储选中的文件ID
|
selectedFiles: [], // 存储选中的文件ID
|
||||||
lastSelectedFileIndex: -1, // 记录最后选择的文件索引
|
lastSelectedFileIndex: -1, // 记录最后选择的文件索引
|
||||||
canUndoRename: false
|
canUndoRename: false,
|
||||||
|
moveMode: false, // 是否为移动文件模式
|
||||||
|
moveFileIds: [] // 要移动的文件ID列表
|
||||||
},
|
},
|
||||||
historyParams: {
|
historyParams: {
|
||||||
sortBy: "transfer_time",
|
sortBy: "transfer_time",
|
||||||
@ -1700,7 +1716,7 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return [...taskNames].sort();
|
return this.sortTaskNamesByPinyin([...taskNames]);
|
||||||
},
|
},
|
||||||
taskNames() {
|
taskNames() {
|
||||||
// 从任务列表中提取唯一的任务名称
|
// 从任务列表中提取唯一的任务名称
|
||||||
@ -1715,7 +1731,7 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return [...taskNames].sort();
|
return this.sortTaskNamesByPinyin([...taskNames]);
|
||||||
},
|
},
|
||||||
totalPages() {
|
totalPages() {
|
||||||
// 直接使用后端返回的total_pages
|
// 直接使用后端返回的total_pages
|
||||||
@ -1822,6 +1838,9 @@
|
|||||||
$('#fileSelectModal').on('hidden.bs.modal', () => {
|
$('#fileSelectModal').on('hidden.bs.modal', () => {
|
||||||
this.fileSelect.selectedFiles = [];
|
this.fileSelect.selectedFiles = [];
|
||||||
this.fileSelect.lastSelectedFileIndex = -1;
|
this.fileSelect.lastSelectedFileIndex = -1;
|
||||||
|
// 重置移动模式相关参数
|
||||||
|
this.fileSelect.moveMode = false;
|
||||||
|
this.fileSelect.moveFileIds = [];
|
||||||
});
|
});
|
||||||
|
|
||||||
// 检查本地存储中的标签页状态
|
// 检查本地存储中的标签页状态
|
||||||
@ -1882,6 +1901,9 @@
|
|||||||
$('#fileSelectModal').on('hidden.bs.modal', () => {
|
$('#fileSelectModal').on('hidden.bs.modal', () => {
|
||||||
this.fileSelect.selectedFiles = [];
|
this.fileSelect.selectedFiles = [];
|
||||||
this.fileSelect.lastSelectedFileIndex = -1;
|
this.fileSelect.lastSelectedFileIndex = -1;
|
||||||
|
// 重置移动模式相关参数
|
||||||
|
this.fileSelect.moveMode = false;
|
||||||
|
this.fileSelect.moveFileIds = [];
|
||||||
});
|
});
|
||||||
|
|
||||||
window.addEventListener('beforeunload', this.handleBeforeUnload);
|
window.addEventListener('beforeunload', this.handleBeforeUnload);
|
||||||
@ -1953,6 +1975,14 @@
|
|||||||
document.removeEventListener('click', this.handleOutsideClick);
|
document.removeEventListener('click', this.handleOutsideClick);
|
||||||
},
|
},
|
||||||
methods: {
|
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) {
|
formatShareUrlBanMessage(message) {
|
||||||
if (!message) return message;
|
if (!message) return message;
|
||||||
@ -2052,9 +2082,19 @@
|
|||||||
// 重置页码并切换到新账号的最后访问目录
|
// 重置页码并切换到新账号的最后访问目录
|
||||||
this.fileManager.currentPage = 1;
|
this.fileManager.currentPage = 1;
|
||||||
this.loadFileListWithFallback(newAccountLastFolder);
|
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 = {
|
const params = {
|
||||||
@ -2064,7 +2104,8 @@
|
|||||||
page_size: this.fileManager.pageSize,
|
page_size: this.fileManager.pageSize,
|
||||||
page: this.fileManager.currentPage,
|
page: this.fileManager.currentPage,
|
||||||
account_index: this.fileManager.selectedAccountIndex,
|
account_index: this.fileManager.selectedAccountIndex,
|
||||||
force_refresh: true // 强制刷新参数
|
force_refresh: true, // 强制刷新参数
|
||||||
|
timestamp: Date.now() // 添加时间戳避免缓存
|
||||||
};
|
};
|
||||||
|
|
||||||
axios.get('/file_list', { params })
|
axios.get('/file_list', { params })
|
||||||
@ -2076,16 +2117,163 @@
|
|||||||
this.fileManager.paths = response.data.data.paths || [];
|
this.fileManager.paths = response.data.data.paths || [];
|
||||||
this.fileManager.gotoPage = this.fileManager.currentPage;
|
this.fileManager.gotoPage = this.fileManager.currentPage;
|
||||||
// 移除成功通知
|
// 移除成功通知
|
||||||
} else {
|
|
||||||
this.showToast('刷新失败:' + response.data.message);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检测当前的命名模式
|
// 检测当前的命名模式
|
||||||
this.detectFileManagerNamingMode();
|
this.detectFileManagerNamingMode();
|
||||||
|
} else {
|
||||||
|
// 如果刷新失败且重试次数少于2次,则重试
|
||||||
|
if (retryCount < 2) {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.refreshCurrentFolderCache(retryCount + 1);
|
||||||
|
}, 1000);
|
||||||
|
} else {
|
||||||
|
this.showToast('刷新失败:' + response.data.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
console.error('刷新缓存失败:', error);
|
console.error('刷新缓存失败:', error);
|
||||||
this.showToast('刷新缓存失败,请稍后重试');
|
// 如果网络错误且重试次数少于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)
|
// 任务配置相关的模态框始终使用主账号(索引0)
|
||||||
// 文件整理页面的预览模态框使用选中的账号
|
// 文件整理页面的预览模态框和移动文件模态框使用选中的账号
|
||||||
const modalType = document.getElementById('fileSelectModal').getAttribute('data-modal-type');
|
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) {
|
if (typeof params === 'object' && params !== null) {
|
||||||
@ -3152,7 +3340,7 @@
|
|||||||
this.fileSelect.fileList = [];
|
this.fileSelect.fileList = [];
|
||||||
this.fileSelect.paths = [];
|
this.fileSelect.paths = [];
|
||||||
this.fileSelect.index = index;
|
this.fileSelect.index = index;
|
||||||
// 重置排序状态为默认值
|
// 重置排序状态为默认值 - 选择需转存的文件夹模态框默认修改时间倒序
|
||||||
this.fileSelect.sortBy = "updated_at";
|
this.fileSelect.sortBy = "updated_at";
|
||||||
this.fileSelect.sortOrder = "desc";
|
this.fileSelect.sortOrder = "desc";
|
||||||
|
|
||||||
@ -3171,21 +3359,43 @@
|
|||||||
},
|
},
|
||||||
getShareDetail(retryCount = 0, maxRetries = 1) {
|
getShareDetail(retryCount = 0, maxRetries = 1) {
|
||||||
this.modalLoading = true;
|
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', {
|
axios.post('/get_share_detail', {
|
||||||
shareurl: this.fileSelect.shareurl,
|
shareurl: this.fileSelect.shareurl,
|
||||||
stoken: this.fileSelect.stoken,
|
stoken: this.fileSelect.stoken,
|
||||||
regex: {
|
regex: regexConfig
|
||||||
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
|
|
||||||
}
|
|
||||||
}).then(response => {
|
}).then(response => {
|
||||||
if (response.data.success) {
|
if (response.data.success) {
|
||||||
this.fileSelect.fileList = response.data.data.list;
|
this.fileSelect.fileList = response.data.data.list;
|
||||||
@ -3239,10 +3449,14 @@
|
|||||||
// 命名预览模式下使用重命名列倒序排序
|
// 命名预览模式下使用重命名列倒序排序
|
||||||
this.fileSelect.sortBy = "file_name_re";
|
this.fileSelect.sortBy = "file_name_re";
|
||||||
this.fileSelect.sortOrder = "desc";
|
this.fileSelect.sortOrder = "desc";
|
||||||
} else {
|
} else if (this.fileSelect.selectDir) {
|
||||||
// 其他情况使用修改日期倒序排序
|
// 选择需转存的文件夹模态框:默认修改时间倒序
|
||||||
this.fileSelect.sortBy = "updated_at";
|
this.fileSelect.sortBy = "updated_at";
|
||||||
this.fileSelect.sortOrder = "desc";
|
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)) {
|
if (this.getShareurl(this.fileSelect.shareurl) != this.getShareurl(this.formData.tasklist[index].shareurl)) {
|
||||||
this.fileSelect.stoken = "";
|
this.fileSelect.stoken = "";
|
||||||
@ -3311,24 +3525,122 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
selectCurrentFolder(addTaskname = false) {
|
selectCurrentFolder(addTaskname = false) {
|
||||||
if (this.fileSelect.selectShare) {
|
if (this.fileSelect.moveMode) {
|
||||||
this.formData.tasklist[this.fileSelect.index].shareurl_ban = undefined;
|
// 移动文件模式
|
||||||
// 如果是分享文件夹且文件列表为空,则设置shareurl_ban
|
this.moveFilesToCurrentFolder();
|
||||||
if (!this.fileSelect.fileList || this.fileSelect.fileList.length === 0) {
|
} else if (this.fileSelect.selectShare) {
|
||||||
this.$set(this.formData.tasklist[this.fileSelect.index], "shareurl_ban", "该分享已被删除,无法访问");
|
// 检查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;
|
||||||
}
|
}
|
||||||
this.formData.tasklist[this.fileSelect.index].shareurl = this.fileSelect.shareurl;
|
|
||||||
} else {
|
} else {
|
||||||
// 去掉前导斜杠,避免双斜杠问题
|
// 检查index是否有效
|
||||||
this.formData.tasklist[this.fileSelect.index].savepath = this.fileSelect.paths.map(item => item.name).join("/");
|
if (this.fileSelect.index !== null && this.fileSelect.index >= 0 && this.formData.tasklist[this.fileSelect.index]) {
|
||||||
if (addTaskname) {
|
// 去掉前导斜杠,避免双斜杠问题
|
||||||
this.formData.tasklist[this.fileSelect.index].savepath += "/" + this.formData.tasklist[this.fileSelect.index].taskname
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$('#fileSelectModal').modal('hide')
|
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) {
|
selectStartFid(fid) {
|
||||||
Vue.set(this.formData.tasklist[this.fileSelect.index], 'startfid', 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')
|
$('#fileSelectModal').modal('hide')
|
||||||
},
|
},
|
||||||
getShareurl(shareurl, path = {}) {
|
getShareurl(shareurl, path = {}) {
|
||||||
@ -3705,7 +4017,7 @@
|
|||||||
}
|
}
|
||||||
}).then(response => {
|
}).then(response => {
|
||||||
if (response.data.success && response.data.data.all_task_names) {
|
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 {
|
} else {
|
||||||
// 如果API失败,回退到从当前页记录中提取任务名称
|
// 如果API失败,回退到从当前页记录中提取任务名称
|
||||||
if (this.history.records && this.history.records.length > 0) {
|
if (this.history.records && this.history.records.length > 0) {
|
||||||
@ -3715,7 +4027,7 @@
|
|||||||
taskNames.add(record.task_name);
|
taskNames.add(record.task_name);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.allTaskNames = [...taskNames].sort();
|
this.allTaskNames = this.sortTaskNamesByPinyin([...taskNames]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
@ -3727,7 +4039,7 @@
|
|||||||
taskNames.add(record.task_name);
|
taskNames.add(record.task_name);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.allTaskNames = [...taskNames].sort();
|
this.allTaskNames = this.sortTaskNamesByPinyin([...taskNames]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@ -3859,12 +4171,25 @@
|
|||||||
// 文件夹始终在前
|
// 文件夹始终在前
|
||||||
if (a.dir && !b.dir) return -1;
|
if (a.dir && !b.dir) return -1;
|
||||||
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 (this.fileSelect.sortOrder === 'asc') {
|
const modalType = document.getElementById('fileSelectModal').getAttribute('data-modal-type');
|
||||||
return aValue > bValue ? 1 : -1;
|
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 {
|
} else {
|
||||||
return aValue < bValue ? 1 : -1;
|
// 其他模态框:使用拼音排序
|
||||||
|
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') {
|
if (field === 'file_name_re') {
|
||||||
@ -3884,12 +4209,19 @@
|
|||||||
let aValue, bValue;
|
let aValue, bValue;
|
||||||
// 对于重命名列,优先使用episode_number进行数值排序(如果存在)
|
// 对于重命名列,优先使用episode_number进行数值排序(如果存在)
|
||||||
if (a.episode_number !== undefined && b.episode_number !== undefined) {
|
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 {
|
} else {
|
||||||
// 否则使用重命名后的文件名进行字符串排序
|
// 否则使用重命名后的文件名进行拼音排序
|
||||||
aValue = (a.file_name_re || '').toLowerCase();
|
aValue = pinyinPro.pinyin(a.file_name_re || '', { toneType: 'none', type: 'string' }).toLowerCase();
|
||||||
bValue = (b.file_name_re || '').toLowerCase();
|
bValue = pinyinPro.pinyin(b.file_name_re || '', { toneType: 'none', type: 'string' }).toLowerCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.fileSelect.sortOrder === 'asc') {
|
if (this.fileSelect.sortOrder === 'asc') {
|
||||||
@ -3934,8 +4266,9 @@
|
|||||||
if (a.dir && !b.dir) return -1;
|
if (a.dir && !b.dir) return -1;
|
||||||
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') {
|
if (this.fileSelect.sortOrder === 'asc') {
|
||||||
return aValue > bValue ? 1 : -1;
|
return aValue > bValue ? 1 : -1;
|
||||||
} else {
|
} else {
|
||||||
@ -3958,13 +4291,12 @@
|
|||||||
if (field === 'file_name') {
|
if (field === 'file_name') {
|
||||||
if (a.dir && !b.dir) return -1;
|
if (a.dir && !b.dir) return -1;
|
||||||
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 ka = sortFileByName(a), kb = sortFileByName(b);
|
||||||
if (order === 'asc') {
|
for (let i = 0; i < ka.length; ++i) {
|
||||||
return aValue > bValue ? 1 : -1;
|
if (ka[i] !== kb[i]) return order === 'asc' ? (ka[i] > kb[i] ? 1 : -1) : (ka[i] < kb[i] ? 1 : -1);
|
||||||
} else {
|
|
||||||
return aValue < bValue ? 1 : -1;
|
|
||||||
}
|
}
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
if (field === 'file_name_re') {
|
if (field === 'file_name_re') {
|
||||||
const aHasValidRename = a.file_name_re && a.file_name_re !== '×' && !a.file_name_re.startsWith('×');
|
const aHasValidRename = a.file_name_re && a.file_name_re !== '×' && !a.file_name_re.startsWith('×');
|
||||||
@ -3983,12 +4315,19 @@
|
|||||||
let aValue, bValue;
|
let aValue, bValue;
|
||||||
// 对于重命名列,优先使用episode_number进行数值排序(如果存在)
|
// 对于重命名列,优先使用episode_number进行数值排序(如果存在)
|
||||||
if (a.episode_number !== undefined && b.episode_number !== undefined) {
|
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 {
|
} else {
|
||||||
// 否则使用重命名后的文件名进行字符串排序
|
// 否则使用重命名后的文件名进行拼音排序
|
||||||
aValue = (a.file_name_re || '').toLowerCase();
|
aValue = pinyinPro.pinyin(a.file_name_re || '', { toneType: 'none', type: 'string' }).toLowerCase();
|
||||||
bValue = (b.file_name_re || '').toLowerCase();
|
bValue = pinyinPro.pinyin(b.file_name_re || '', { toneType: 'none', type: 'string' }).toLowerCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (order === 'asc') {
|
if (order === 'asc') {
|
||||||
@ -4033,8 +4372,9 @@
|
|||||||
if (a.dir && !b.dir) return -1;
|
if (a.dir && !b.dir) return -1;
|
||||||
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') {
|
if (order === 'asc') {
|
||||||
return aValue > bValue ? 1 : -1;
|
return aValue > bValue ? 1 : -1;
|
||||||
} else {
|
} else {
|
||||||
@ -4890,8 +5230,11 @@
|
|||||||
if (previewItem) {
|
if (previewItem) {
|
||||||
// 设置重命名字段
|
// 设置重命名字段
|
||||||
file.file_name_re = previewItem.new_name;
|
file.file_name_re = previewItem.new_name;
|
||||||
|
// 设置集数字段用于排序
|
||||||
|
file.episode_number = previewItem.episode_number;
|
||||||
} else {
|
} else {
|
||||||
file.file_name_re = null;
|
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) {
|
saveRenameFile(file) {
|
||||||
if (!file._editing) return;
|
if (!file._editing) return;
|
||||||
@ -5490,6 +5866,9 @@
|
|||||||
$('#fileSelectModal').on('hidden.bs.modal', () => {
|
$('#fileSelectModal').on('hidden.bs.modal', () => {
|
||||||
this.fileSelect.selectedFiles = [];
|
this.fileSelect.selectedFiles = [];
|
||||||
this.fileSelect.lastSelectedFileIndex = -1;
|
this.fileSelect.lastSelectedFileIndex = -1;
|
||||||
|
// 重置移动模式相关参数
|
||||||
|
this.fileSelect.moveMode = false;
|
||||||
|
this.fileSelect.moveFileIds = [];
|
||||||
});
|
});
|
||||||
|
|
||||||
// 检查本地存储中的标签页状态
|
// 检查本地存储中的标签页状态
|
||||||
@ -5547,6 +5926,9 @@
|
|||||||
$('#fileSelectModal').on('hidden.bs.modal', () => {
|
$('#fileSelectModal').on('hidden.bs.modal', () => {
|
||||||
this.fileSelect.selectedFiles = [];
|
this.fileSelect.selectedFiles = [];
|
||||||
this.fileSelect.lastSelectedFileIndex = -1;
|
this.fileSelect.lastSelectedFileIndex = -1;
|
||||||
|
// 重置移动模式相关参数
|
||||||
|
this.fileSelect.moveMode = false;
|
||||||
|
this.fileSelect.moveFileIds = [];
|
||||||
});
|
});
|
||||||
|
|
||||||
window.addEventListener('beforeunload', this.handleBeforeUnload);
|
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__))))
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
try:
|
try:
|
||||||
from quark_auto_save import sort_file_by_name
|
from quark_auto_save import sort_file_by_name
|
||||||
|
from app.utils.pinyin_sort import get_filename_pinyin_sort_key
|
||||||
except ImportError:
|
except ImportError:
|
||||||
# 如果无法导入,提供一个简单的排序函数作为替代
|
# 如果无法导入,提供一个简单的排序函数作为替代
|
||||||
def sort_file_by_name(file):
|
def sort_file_by_name(file):
|
||||||
if isinstance(file, dict):
|
if isinstance(file, dict):
|
||||||
filename = file.get("file_name", "")
|
filename = file.get("file_name", "")
|
||||||
|
update_time = file.get("updated_at", 0)
|
||||||
else:
|
else:
|
||||||
filename = file
|
filename = file
|
||||||
# 简单排序,主要通过文件名进行
|
update_time = 0
|
||||||
return filename
|
# 简单排序,主要通过文件名进行(使用拼音排序)
|
||||||
|
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:
|
class Aria2:
|
||||||
|
|||||||
@ -42,20 +42,28 @@ def sort_file_by_name(file):
|
|||||||
通用的文件排序函数,用于根据文件名智能排序
|
通用的文件排序函数,用于根据文件名智能排序
|
||||||
支持多种格式的日期、期数、集数等提取和排序
|
支持多种格式的日期、期数、集数等提取和排序
|
||||||
使用多级排序键,按日期、期数、上中下顺序排序
|
使用多级排序键,按日期、期数、上中下顺序排序
|
||||||
如果以上均无法提取,则使用文件更新时间作为最后排序依据
|
如果以上均无法提取,则使用文件更新时间和拼音排序作为最后排序依据
|
||||||
"""
|
"""
|
||||||
if isinstance(file, dict) and file.get("dir", False): # 跳过文件夹
|
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):
|
if isinstance(file, dict):
|
||||||
filename = file.get("file_name", "")
|
filename = file.get("file_name", "")
|
||||||
# 获取更新时间作为最后排序依据
|
# 获取更新时间作为第四级排序依据
|
||||||
update_time = file.get("updated_at", 0)
|
update_time = file.get("updated_at", 0)
|
||||||
else:
|
else:
|
||||||
filename = file
|
filename = file
|
||||||
update_time = 0
|
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]
|
file_name_without_ext = os.path.splitext(filename)[0]
|
||||||
|
|
||||||
@ -212,8 +220,8 @@ def sort_file_by_name(file):
|
|||||||
elif re.search(r'下[集期话部篇]?|[集期话部篇]下', filename):
|
elif re.search(r'下[集期话部篇]?|[集期话部篇]下', filename):
|
||||||
segment_value = 3
|
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:
|
if month and day and 1 <= month <= 12 and 1 <= day <= 31:
|
||||||
filename_without_dates = filename_without_dates.replace(date_str, " ")
|
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格式
|
# 优先匹配SxxExx格式
|
||||||
match_s_e = re.search(r'[Ss](\d+)[Ee](\d+)', filename_without_dates)
|
match_s_e = re.search(r'[Ss](\d+)[Ee](\d+)', filename_without_dates)
|
||||||
if match_s_e:
|
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))
|
episode_num = int(num_match.group(1))
|
||||||
# 检查提取的数字是否可能是日期
|
# 检查提取的数字是否可能是日期
|
||||||
if not is_date_format(str(episode_num)):
|
if not is_date_format(str(episode_num)):
|
||||||
|
# 检查是否是过大的数字(可能是时间戳、文件大小等)
|
||||||
|
if episode_num > 9999:
|
||||||
|
return None # 跳过过大的数字
|
||||||
return episode_num
|
return episode_num
|
||||||
|
|
||||||
return None
|
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']):
|
if any(lower_name.endswith(ext) for ext in ['.srt', '.ass', '.ssa', '.vtt']):
|
||||||
return "💬"
|
return "💬"
|
||||||
|
|
||||||
|
# 歌词文件
|
||||||
|
if any(lower_name.endswith(ext) for ext in ['.lrc']):
|
||||||
|
return "💬"
|
||||||
|
|
||||||
# 默认图标(其他文件类型)
|
# 默认图标(其他文件类型)
|
||||||
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:
|
class Config:
|
||||||
# 下载配置
|
# 下载配置
|
||||||
@ -1081,6 +1121,21 @@ class Quark:
|
|||||||
).json()
|
).json()
|
||||||
return response
|
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):
|
def rename(self, fid, file_name):
|
||||||
url = f"{self.BASE_URL}/1/clouddrive/file/rename"
|
url = f"{self.BASE_URL}/1/clouddrive/file/rename"
|
||||||
querystring = {"pr": "ucpro", "fr": "pc", "uc_param_str": ""}
|
querystring = {"pr": "ucpro", "fr": "pc", "uc_param_str": ""}
|
||||||
@ -1099,6 +1154,21 @@ class Quark:
|
|||||||
).json()
|
).json()
|
||||||
return response
|
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):
|
def recycle_list(self, page=1, size=30):
|
||||||
url = f"{self.BASE_URL}/1/clouddrive/file/recycle/list"
|
url = f"{self.BASE_URL}/1/clouddrive/file/recycle/list"
|
||||||
querystring = {
|
querystring = {
|
||||||
@ -1389,12 +1459,13 @@ class Quark:
|
|||||||
return False
|
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:
|
Args:
|
||||||
task: 任务信息
|
task: 任务信息
|
||||||
rename_log: 重命名日志,格式为 "重命名: 旧名 → 新名"
|
rename_log: 重命名日志,格式为 "重命名: 旧名 → 新名"
|
||||||
|
actual_file_names: 实际文件名映射字典,用于修正显示的文件名
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# 使用字符串分割方法提取文件名,更可靠地获取完整文件名
|
# 使用字符串分割方法提取文件名,更可靠地获取完整文件名
|
||||||
@ -1407,25 +1478,30 @@ class Quark:
|
|||||||
if " → " not in parts:
|
if " → " not in parts:
|
||||||
return False
|
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
|
return False
|
||||||
|
|
||||||
# 处理可能的截断标记,只保留实际文件名部分
|
# 处理可能的截断标记,只保留实际文件名部分
|
||||||
# 注意:只有明确是失败消息才应该截断
|
# 注意:只有明确是失败消息才应该截断
|
||||||
if " 失败," in new_name:
|
if " 失败," in expected_new_name:
|
||||||
new_name = new_name.split(" 失败,")[0]
|
expected_new_name = expected_new_name.split(" 失败,")[0]
|
||||||
|
|
||||||
# 去除首尾空格
|
# 去除首尾空格
|
||||||
old_name = old_name.strip()
|
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
|
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()
|
db = RecordDB()
|
||||||
|
|
||||||
@ -1446,13 +1522,13 @@ class Quark:
|
|||||||
# 如果找到了匹配的记录,使用file_id进行更新
|
# 如果找到了匹配的记录,使用file_id进行更新
|
||||||
file_id = result[0] if result else ""
|
file_id = result[0] if result else ""
|
||||||
|
|
||||||
# 更新记录
|
# 更新记录,使用实际的文件名
|
||||||
if file_id:
|
if file_id:
|
||||||
# 使用file_id更新
|
# 使用file_id更新
|
||||||
updated = db.update_renamed_to(
|
updated = db.update_renamed_to(
|
||||||
file_id=file_id,
|
file_id=file_id,
|
||||||
original_name="", # 不使用原文件名,因为已有file_id
|
original_name="", # 不使用原文件名,因为已有file_id
|
||||||
renamed_to=new_name,
|
renamed_to=actual_new_name, # 使用实际的文件名
|
||||||
task_name=task_name,
|
task_name=task_name,
|
||||||
save_path=save_path
|
save_path=save_path
|
||||||
)
|
)
|
||||||
@ -1461,7 +1537,7 @@ class Quark:
|
|||||||
updated = db.update_renamed_to(
|
updated = db.update_renamed_to(
|
||||||
file_id="", # 不使用file_id查询,因为在日志中无法获取
|
file_id="", # 不使用file_id查询,因为在日志中无法获取
|
||||||
original_name=old_name,
|
original_name=old_name,
|
||||||
renamed_to=new_name,
|
renamed_to=actual_new_name, # 使用实际的文件名
|
||||||
task_name=task_name,
|
task_name=task_name,
|
||||||
save_path=save_path
|
save_path=save_path
|
||||||
)
|
)
|
||||||
@ -1482,9 +1558,79 @@ class Quark:
|
|||||||
task: 任务信息
|
task: 任务信息
|
||||||
rename_logs: 重命名日志列表
|
rename_logs: 重命名日志列表
|
||||||
"""
|
"""
|
||||||
|
# 获取实际的文件名映射
|
||||||
|
actual_file_names = self.get_actual_file_names_from_directory(task, rename_logs)
|
||||||
|
|
||||||
for log in rename_logs:
|
for log in rename_logs:
|
||||||
if "重命名:" in log and "→" in log and "失败" not in log:
|
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):
|
def check_file_exists_in_records(self, file_id, task=None):
|
||||||
"""检查文件ID是否存在于转存记录中
|
"""检查文件ID是否存在于转存记录中
|
||||||
@ -1659,6 +1805,10 @@ class Quark:
|
|||||||
"dir_path": [] # 保存目录路径
|
"dir_path": [] # 保存目录路径
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# 对文件列表进行排序,使用全局文件排序函数的倒序排序
|
||||||
|
# 这样可以确保起始文件过滤逻辑正确工作
|
||||||
|
share_file_list.sort(key=sort_file_by_name, reverse=True)
|
||||||
|
|
||||||
# 应用过滤词过滤
|
# 应用过滤词过滤
|
||||||
if task.get("filterwords"):
|
if task.get("filterwords"):
|
||||||
# 记录过滤前的文件总数(包括文件夹)
|
# 记录过滤前的文件总数(包括文件夹)
|
||||||
@ -1808,6 +1958,9 @@ class Quark:
|
|||||||
# 预先过滤掉已经存在的文件(按大小和扩展名比对)
|
# 预先过滤掉已经存在的文件(按大小和扩展名比对)
|
||||||
# 只保留文件,不保留文件夹
|
# 只保留文件,不保留文件夹
|
||||||
filtered_share_files = []
|
filtered_share_files = []
|
||||||
|
start_fid = task.get("startfid", "")
|
||||||
|
start_file_found = False
|
||||||
|
|
||||||
for share_file in share_file_list:
|
for share_file in share_file_list:
|
||||||
if share_file["dir"]:
|
if share_file["dir"]:
|
||||||
# 顺序命名模式下,未设置update_subdir时不处理文件夹
|
# 顺序命名模式下,未设置update_subdir时不处理文件夹
|
||||||
@ -1819,9 +1972,15 @@ class Quark:
|
|||||||
# 文件ID已存在于记录中,跳过处理
|
# 文件ID已存在于记录中,跳过处理
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# 指定文件开始订阅/到达指定文件(含)结束历遍
|
# 改进的起始文件过滤逻辑
|
||||||
if share_file["fid"] == task.get("startfid", ""):
|
if start_fid:
|
||||||
break
|
if share_file["fid"] == start_fid:
|
||||||
|
start_file_found = True
|
||||||
|
break # 找到起始文件,停止遍历
|
||||||
|
# 如果还没找到起始文件,继续添加到转存列表
|
||||||
|
else:
|
||||||
|
# 没有设置起始文件,处理所有文件
|
||||||
|
pass
|
||||||
|
|
||||||
file_size = share_file.get("size", 0)
|
file_size = share_file.get("size", 0)
|
||||||
file_ext = os.path.splitext(share_file["file_name"])[1].lower()
|
file_ext = os.path.splitext(share_file["file_name"])[1].lower()
|
||||||
@ -1893,9 +2052,7 @@ class Quark:
|
|||||||
# print(f"跳过已存在的文件: {save_name}")
|
# print(f"跳过已存在的文件: {save_name}")
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# 指定文件开始订阅/到达指定文件(含)结束历遍
|
# 这里不需要再次检查起始文件,因为在前面的过滤中已经处理了
|
||||||
if share_file["fid"] == task.get("startfid", ""):
|
|
||||||
break
|
|
||||||
|
|
||||||
# 处理子文件夹
|
# 处理子文件夹
|
||||||
for share_file in share_file_list:
|
for share_file in share_file_list:
|
||||||
@ -2021,6 +2178,20 @@ class Quark:
|
|||||||
"updated_at": update_time,
|
"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:
|
for share_file in share_file_list:
|
||||||
# 检查文件ID是否存在于转存记录中
|
# 检查文件ID是否存在于转存记录中
|
||||||
@ -2029,10 +2200,6 @@ class Quark:
|
|||||||
# 文件ID已存在于记录中,跳过处理
|
# 文件ID已存在于记录中,跳过处理
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# 指定文件开始订阅/到达指定文件(含)结束历遍
|
|
||||||
if share_file["fid"] == task.get("startfid", ""):
|
|
||||||
break
|
|
||||||
|
|
||||||
# 检查文件是否已存在(通过大小和扩展名)- 新增的文件查重逻辑
|
# 检查文件是否已存在(通过大小和扩展名)- 新增的文件查重逻辑
|
||||||
is_duplicate = False
|
is_duplicate = False
|
||||||
if not share_file["dir"]: # 文件夹不进行内容查重
|
if not share_file["dir"]: # 文件夹不进行内容查重
|
||||||
@ -2429,9 +2596,13 @@ class Quark:
|
|||||||
)
|
)
|
||||||
|
|
||||||
# 修复文件树显示问题 - 防止文件名重复重复显示
|
# 修复文件树显示问题 - 防止文件名重复重复显示
|
||||||
# 如果save_name与original_name相似(如:一个是"你好,星期六 - 2025-04-05.mp4",另一个是"20250405期.mp4")
|
# 对于顺序命名和剧集命名模式,转存时使用原文件名显示
|
||||||
# 则只显示save_name,避免重复
|
# 因为实际文件是以原名保存的,重命名在后续步骤进行
|
||||||
display_name = item['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']
|
||||||
|
|
||||||
# 确保只显示文件/文件夹名,而不是完整路径
|
# 确保只显示文件/文件夹名,而不是完整路径
|
||||||
if "/" in display_name:
|
if "/" in display_name:
|
||||||
@ -2445,22 +2616,24 @@ class Quark:
|
|||||||
# 检查节点是否已存在于树中,避免重复添加
|
# 检查节点是否已存在于树中,避免重复添加
|
||||||
if not tree.contains(item["fid"]):
|
if not tree.contains(item["fid"]):
|
||||||
tree.create_node(
|
tree.create_node(
|
||||||
f"{icon}{display_name}",
|
display_name, # 只存储文件名,不包含图标
|
||||||
item["fid"],
|
item["fid"],
|
||||||
parent=pdir_fid,
|
parent=pdir_fid,
|
||||||
data={
|
data={
|
||||||
"fid": f"{query_task_return['data']['save_as']['save_as_top_fids'][index]}",
|
"fid": f"{query_task_return['data']['save_as']['save_as_top_fids'][index]}",
|
||||||
"path": f"{savepath}/{item['save_name']}",
|
"path": f"{savepath}/{item['save_name']}",
|
||||||
"is_dir": item["dir"],
|
"is_dir": item["dir"],
|
||||||
|
"icon": icon, # 将图标存储在data中
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
# 保存转存记录到数据库
|
# 保存转存记录到数据库
|
||||||
if not item["dir"]: # 只记录文件,不记录文件夹
|
if not item["dir"]: # 只记录文件,不记录文件夹
|
||||||
|
# 转存时先用原文件名记录,重命名后再更新
|
||||||
self.create_transfer_record(
|
self.create_transfer_record(
|
||||||
task=task,
|
task=task,
|
||||||
file_info=item,
|
file_info=item,
|
||||||
renamed_to=item.get("save_name", item["file_name"])
|
renamed_to=item["file_name"] # 转存时使用原文件名
|
||||||
)
|
)
|
||||||
|
|
||||||
# 移除通知生成,由do_save函数统一处理
|
# 移除通知生成,由do_save函数统一处理
|
||||||
@ -2500,27 +2673,77 @@ class Quark:
|
|||||||
non_dir_files = [f for f in dir_file_list if not f.get("dir", False)]
|
non_dir_files = [f for f in dir_file_list if not f.get("dir", False)]
|
||||||
is_empty_dir = len(non_dir_files) == 0
|
is_empty_dir = len(non_dir_files) == 0
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# 找出当前最大序号
|
# 找出当前最大序号
|
||||||
max_sequence = 0
|
max_sequence = 0
|
||||||
if not is_empty_dir: # 只有在目录非空时才寻找最大序号
|
# 先检查目录中是否有符合命名规则的文件
|
||||||
for dir_file in dir_file_list:
|
has_matching_files = False
|
||||||
if sequence_pattern == "{}":
|
|
||||||
# 对于单独的{},直接尝试匹配整个文件名是否为数字
|
for dir_file in dir_file_list:
|
||||||
file_name_without_ext = os.path.splitext(dir_file["file_name"])[0]
|
if dir_file.get("dir", False):
|
||||||
if file_name_without_ext.isdigit():
|
continue # 跳过文件夹
|
||||||
# 增加判断:如果是日期格式的纯数字,不应被视为序号
|
|
||||||
if not is_date_format(file_name_without_ext):
|
if sequence_pattern == "{}":
|
||||||
|
# 对于单独的{},直接尝试匹配整个文件名是否为数字
|
||||||
|
file_name_without_ext = os.path.splitext(dir_file["file_name"])[0]
|
||||||
|
if file_name_without_ext.isdigit():
|
||||||
|
# 增加判断:如果是日期格式的纯数字,不应被视为序号
|
||||||
|
if not is_date_format(file_name_without_ext):
|
||||||
|
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:
|
try:
|
||||||
current_seq = int(file_name_without_ext)
|
seq_num = int(matches.group(1))
|
||||||
max_sequence = max(max_sequence, current_seq)
|
max_sequence_from_records = max(max_sequence_from_records, seq_num)
|
||||||
except (ValueError, IndexError):
|
except (ValueError, IndexError):
|
||||||
pass
|
pass
|
||||||
elif matches := re.match(regex_pattern, dir_file["file_name"]):
|
|
||||||
try:
|
# 使用记录中的最大序号
|
||||||
current_seq = int(matches.group(1))
|
max_sequence = max_sequence_from_records
|
||||||
max_sequence = max(max_sequence, current_seq)
|
db.close()
|
||||||
except (IndexError, ValueError):
|
except Exception as e:
|
||||||
pass
|
max_sequence = 0
|
||||||
|
|
||||||
# 实现高级排序算法
|
# 实现高级排序算法
|
||||||
def extract_sorting_value(file):
|
def extract_sorting_value(file):
|
||||||
@ -2611,12 +2834,11 @@ class Quark:
|
|||||||
is_rename_count += 1
|
is_rename_count += 1
|
||||||
|
|
||||||
# 更新重命名记录到数据库(只更新renamed_to字段)
|
# 更新重命名记录到数据库(只更新renamed_to字段)
|
||||||
# 不在这里直接调用update_transfer_record,而是在do_save中统一处理
|
self.update_transfer_record(
|
||||||
# self.update_transfer_record(
|
task=task,
|
||||||
# task=task,
|
file_info=dir_file,
|
||||||
# file_info=dir_file,
|
renamed_to=save_name
|
||||||
# renamed_to=save_name
|
)
|
||||||
# )
|
|
||||||
else:
|
else:
|
||||||
error_msg = rename_return.get("message", "未知错误")
|
error_msg = rename_return.get("message", "未知错误")
|
||||||
rename_log = f"重命名: {dir_file['file_name']} → {save_name} 失败,{error_msg}"
|
rename_log = f"重命名: {dir_file['file_name']} → {save_name} 失败,{error_msg}"
|
||||||
@ -2719,8 +2941,15 @@ class Quark:
|
|||||||
print("分享为空,文件已被分享者删除")
|
print("分享为空,文件已被分享者删除")
|
||||||
return False, []
|
return False, []
|
||||||
|
|
||||||
|
# 在剧集命名模式中,需要先对文件列表进行排序,然后再应用起始文件过滤
|
||||||
|
# 使用全局排序函数进行排序(倒序,最新的在前)
|
||||||
|
share_file_list = sorted(share_file_list, key=sort_file_by_name, reverse=True)
|
||||||
|
|
||||||
# 预先过滤分享文件列表,去除已存在的文件
|
# 预先过滤分享文件列表,去除已存在的文件
|
||||||
filtered_share_files = []
|
filtered_share_files = []
|
||||||
|
start_fid = task.get("startfid", "")
|
||||||
|
start_file_found = False
|
||||||
|
|
||||||
for share_file in share_file_list:
|
for share_file in share_file_list:
|
||||||
if share_file["dir"]:
|
if share_file["dir"]:
|
||||||
# 处理子目录
|
# 处理子目录
|
||||||
@ -2734,9 +2963,15 @@ class Quark:
|
|||||||
# 文件ID已存在于记录中,跳过处理
|
# 文件ID已存在于记录中,跳过处理
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# 指定文件开始订阅/到达指定文件(含)结束历遍
|
# 改进的起始文件过滤逻辑
|
||||||
if share_file["fid"] == task.get("startfid", ""):
|
if start_fid:
|
||||||
break
|
if share_file["fid"] == start_fid:
|
||||||
|
start_file_found = True
|
||||||
|
break # 找到起始文件,停止遍历
|
||||||
|
# 如果还没找到起始文件,继续添加到转存列表
|
||||||
|
else:
|
||||||
|
# 没有设置起始文件,处理所有文件
|
||||||
|
pass
|
||||||
|
|
||||||
# 从共享文件中提取剧集号
|
# 从共享文件中提取剧集号
|
||||||
episode_num = extract_episode_number_local(share_file["file_name"])
|
episode_num = extract_episode_number_local(share_file["file_name"])
|
||||||
@ -2901,10 +3136,11 @@ class Quark:
|
|||||||
# 保存转存记录到数据库
|
# 保存转存记录到数据库
|
||||||
for saved_item in need_save_list:
|
for saved_item in need_save_list:
|
||||||
if not saved_item.get("dir", False): # 只记录文件,不记录文件夹
|
if not saved_item.get("dir", False): # 只记录文件,不记录文件夹
|
||||||
|
# 转存时先用原文件名记录,重命名后再更新
|
||||||
self.create_transfer_record(
|
self.create_transfer_record(
|
||||||
task=task,
|
task=task,
|
||||||
file_info=saved_item,
|
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
|
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']}")
|
savepath = re.sub(r"/{2,}", "/", f"/{task['savepath']}")
|
||||||
if account.savepath_fid.get(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:
|
for log in rename_logs:
|
||||||
# 格式:重命名: 旧名 → 新名
|
# 格式:重命名: 旧名 → 新名
|
||||||
match = re.search(r'重命名: (.*?) → (.+?)($|\s|,|失败)', log)
|
match = re.search(r'重命名: (.*?) → (.+?)($|\s|,|失败)', log)
|
||||||
if match:
|
if match:
|
||||||
old_name = match.group(1).strip()
|
old_name = match.group(1).strip()
|
||||||
new_name = match.group(2).strip()
|
expected_new_name = match.group(2).strip()
|
||||||
renamed_files[old_name] = new_name
|
expected_renamed_files[old_name] = expected_new_name
|
||||||
|
|
||||||
# 获取文件列表,只添加重命名的文件
|
# 获取文件列表,只添加重命名的文件
|
||||||
fresh_dir_file_list = account.ls_dir(account.savepath_fid[savepath])
|
fresh_dir_file_list = account.ls_dir(account.savepath_fid[savepath])
|
||||||
|
|
||||||
# 添加重命名后的文件到树中
|
# 添加实际存在的文件到树中
|
||||||
for file in fresh_dir_file_list:
|
for file in fresh_dir_file_list:
|
||||||
if not file["dir"]: # 只处理文件
|
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(
|
new_tree.create_node(
|
||||||
file["file_name"],
|
file["file_name"], # 使用实际存在的文件名
|
||||||
file["fid"],
|
file["fid"],
|
||||||
parent="root",
|
parent="root",
|
||||||
data={
|
data={
|
||||||
@ -3559,30 +3812,33 @@ def do_save(account, tasklist=[]):
|
|||||||
|
|
||||||
# 按文件名排序
|
# 按文件名排序
|
||||||
if is_special_sequence:
|
if is_special_sequence:
|
||||||
# 对于顺序命名模式,使用重命名日志来获取新增的文件
|
# 对于顺序命名模式,直接使用文件树中的实际文件名
|
||||||
if rename_logs:
|
if rename_logs:
|
||||||
# 从重命名日志提取新旧文件名
|
# 直接显示文件树中的实际文件名(已经是重命名后的结果)
|
||||||
renamed_files = {}
|
# 按照文件名中的序号进行排序
|
||||||
for log in rename_logs:
|
def extract_sequence_number(node):
|
||||||
# 格式:重命名: 旧名 → 新名
|
filename = remove_file_icons(node.tag)
|
||||||
match = re.search(r'重命名: (.*?) → (.+?)($|\s|,|失败)', log)
|
# 尝试从文件名中提取序号,如 "乘风2025 - S06E01.flac" -> 1
|
||||||
|
import re
|
||||||
|
match = re.search(r'E(\d+)', filename)
|
||||||
if match:
|
if match:
|
||||||
old_name = match.group(1).strip()
|
return int(match.group(1))
|
||||||
new_name = match.group(2).strip()
|
# 如果没有找到E序号,尝试其他模式
|
||||||
renamed_files[old_name] = new_name
|
match = re.search(r'(\d+)', filename)
|
||||||
|
if match:
|
||||||
|
return int(match.group(1))
|
||||||
|
return 0
|
||||||
|
|
||||||
# 只显示重命名的文件
|
# 按序号排序
|
||||||
for node in file_nodes:
|
sorted_nodes = sorted(file_nodes, key=extract_sequence_number)
|
||||||
# 获取原始文件名(去除已有图标)
|
|
||||||
orig_filename = node.tag.lstrip("🎞️")
|
for node in sorted_nodes:
|
||||||
# 检查此文件是否在重命名日志中
|
# 获取实际文件名(去除已有图标)
|
||||||
if orig_filename in renamed_files:
|
actual_filename = remove_file_icons(node.tag)
|
||||||
# 使用重命名后的文件名
|
# 获取适当的图标
|
||||||
new_filename = renamed_files[orig_filename]
|
icon = get_file_icon(actual_filename, is_dir=node.data.get("is_dir", False))
|
||||||
# 获取适当的图标
|
# 添加到显示列表
|
||||||
icon = get_file_icon(new_filename, is_dir=node.data.get("is_dir", False))
|
display_files.append((f"{icon} {actual_filename}", node))
|
||||||
# 添加到显示列表
|
|
||||||
display_files.append((f"{icon} {new_filename}", node))
|
|
||||||
else:
|
else:
|
||||||
# 如果没有重命名日志,使用原来的顺序命名逻辑
|
# 如果没有重命名日志,使用原来的顺序命名逻辑
|
||||||
if task.get("use_sequence_naming") and task.get("sequence_naming"):
|
if task.get("use_sequence_naming") and task.get("sequence_naming"):
|
||||||
@ -3594,7 +3850,7 @@ def do_save(account, tasklist=[]):
|
|||||||
# 提取序号(从1开始)
|
# 提取序号(从1开始)
|
||||||
file_num = i + 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]
|
file_ext = os.path.splitext(orig_filename)[1]
|
||||||
# 生成新的文件名(使用顺序命名模式)
|
# 生成新的文件名(使用顺序命名模式)
|
||||||
if sequence_pattern == "{}":
|
if sequence_pattern == "{}":
|
||||||
@ -3608,7 +3864,7 @@ def do_save(account, tasklist=[]):
|
|||||||
display_files.append((f"{icon} {new_filename}", node))
|
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"):
|
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:
|
for node in file_nodes:
|
||||||
# 获取原始文件名(去除已有图标)
|
# 获取原始文件名(去除已有图标)
|
||||||
orig_filename = node.tag.lstrip("🎞️")
|
orig_filename = remove_file_icons(node.tag)
|
||||||
# 检查此文件是否在重命名日志中
|
# 检查此文件是否在重命名日志中
|
||||||
if orig_filename in renamed_files:
|
if orig_filename in renamed_files:
|
||||||
# 使用重命名后的文件名
|
# 使用重命名后的文件名
|
||||||
@ -3646,9 +3902,9 @@ def do_save(account, tasklist=[]):
|
|||||||
if not display_files:
|
if not display_files:
|
||||||
for node in sorted(file_nodes, key=lambda node: node.tag):
|
for node in sorted(file_nodes, key=lambda node: node.tag):
|
||||||
# 获取原始文件名(去除已有图标)
|
# 获取原始文件名(去除已有图标)
|
||||||
orig_filename = node.tag.lstrip("🎞️")
|
orig_filename = remove_file_icons(node.tag)
|
||||||
# 添加适当的图标
|
# 优先使用存储在data中的图标,否则重新计算
|
||||||
icon = get_file_icon(orig_filename, is_dir=node.data.get("is_dir", False))
|
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))
|
display_files.append((f"{icon} {orig_filename}", node))
|
||||||
else:
|
else:
|
||||||
# 其他模式:显示原始文件名
|
# 其他模式:显示原始文件名
|
||||||
@ -3759,7 +4015,7 @@ def do_save(account, tasklist=[]):
|
|||||||
|
|
||||||
for node in sorted(all_file_nodes, key=lambda node: node.tag):
|
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)
|
icon = get_file_icon(orig_filename, is_dir=False)
|
||||||
|
|
||||||
@ -3872,7 +4128,7 @@ def do_save(account, tasklist=[]):
|
|||||||
|
|
||||||
# 排序函数,使用文件节点作为输入
|
# 排序函数,使用文件节点作为输入
|
||||||
def sort_nodes(nodes):
|
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 = {
|
final_display_files = {
|
||||||
@ -3905,7 +4161,7 @@ def do_save(account, tasklist=[]):
|
|||||||
# 检查根目录是否有新增文件
|
# 检查根目录是否有新增文件
|
||||||
root_new_files = []
|
root_new_files = []
|
||||||
for node in files_by_dir["root"]:
|
for node in files_by_dir["root"]:
|
||||||
file_name = node.tag.lstrip("🎞️")
|
file_name = remove_file_icons(node.tag)
|
||||||
# 判断是否为新增文件
|
# 判断是否为新增文件
|
||||||
is_new_file = False
|
is_new_file = False
|
||||||
|
|
||||||
@ -3954,7 +4210,7 @@ def do_save(account, tasklist=[]):
|
|||||||
|
|
||||||
# 检查该目录下的文件是否是新文件
|
# 检查该目录下的文件是否是新文件
|
||||||
for file_node in dir_files:
|
for file_node in dir_files:
|
||||||
file_name = file_node.tag.lstrip("🎞️")
|
file_name = remove_file_icons(file_node.tag)
|
||||||
# 判断是否为新增文件
|
# 判断是否为新增文件
|
||||||
is_new_file = False
|
is_new_file = False
|
||||||
|
|
||||||
@ -4223,7 +4479,7 @@ def do_save(account, tasklist=[]):
|
|||||||
|
|
||||||
# 显示文件
|
# 显示文件
|
||||||
file_prefix = prefix + ("└── " if is_file_last else "├── ")
|
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)
|
icon = get_file_icon(file_name, is_dir=False)
|
||||||
add_notify(format_file_display(file_prefix, icon, file_name))
|
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"]]
|
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:
|
for log in rename_logs:
|
||||||
# 格式:重命名: 旧名 → 新名
|
# 格式:重命名: 旧名 → 新名
|
||||||
if "重命名:" in log and " → " in log:
|
if "重命名:" in log and " → " in log:
|
||||||
@ -4258,22 +4517,27 @@ def do_save(account, tasklist=[]):
|
|||||||
parts = log.split("重命名:", 1)[1].strip()
|
parts = log.split("重命名:", 1)[1].strip()
|
||||||
# 再按箭头分割
|
# 再按箭头分割
|
||||||
if " → " in parts:
|
if " → " in parts:
|
||||||
old_name, new_name = parts.split(" → ", 1)
|
old_name, expected_new_name = parts.split(" → ", 1)
|
||||||
# 只处理失败信息,不截断正常文件名
|
# 只处理失败信息,不截断正常文件名
|
||||||
if " 失败," in new_name:
|
if " 失败," in expected_new_name:
|
||||||
new_name = new_name.split(" 失败,")[0]
|
expected_new_name = expected_new_name.split(" 失败,")[0]
|
||||||
# 去除首尾空格
|
# 去除首尾空格
|
||||||
old_name = old_name.strip()
|
old_name = old_name.strip()
|
||||||
new_name = new_name.strip()
|
expected_new_name = expected_new_name.strip()
|
||||||
renamed_files[old_name] = new_name
|
expected_renamed_files[old_name] = expected_new_name
|
||||||
|
|
||||||
# 确保至少显示重命名后的文件
|
# 确保至少显示实际存在的文件
|
||||||
display_files = []
|
display_files = []
|
||||||
|
|
||||||
# 添加所有重命名的目标文件
|
# 添加所有实际存在的转存文件
|
||||||
for old_name, new_name in renamed_files.items():
|
for old_name in expected_renamed_files.keys():
|
||||||
if new_name not in display_files:
|
# 获取实际的文件名
|
||||||
display_files.append(new_name)
|
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'):
|
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"]]
|
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:
|
for log in rename_logs:
|
||||||
# 格式:重命名: 旧名 → 新名
|
# 格式:重命名: 旧名 → 新名
|
||||||
match = re.search(r'重命名: (.*?) → (.+?)($|\s|,|失败)', log)
|
match = re.search(r'重命名: (.*?) → (.+?)($|\s|,|失败)', log)
|
||||||
if match:
|
if match:
|
||||||
old_name = match.group(1).strip()
|
old_name = match.group(1).strip()
|
||||||
new_name = match.group(2).strip()
|
expected_new_name = match.group(2).strip()
|
||||||
renamed_files[old_name] = new_name
|
expected_renamed_files[old_name] = expected_new_name
|
||||||
|
|
||||||
# 只显示重命名的文件
|
# 只显示实际存在的转存文件
|
||||||
display_files = []
|
display_files = []
|
||||||
for file in file_nodes:
|
for old_name in expected_renamed_files.keys():
|
||||||
if file["file_name"] in renamed_files:
|
# 获取实际的文件名
|
||||||
display_files.append(renamed_files[file["file_name"]])
|
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:
|
if not display_files:
|
||||||
|
|||||||
@ -2,3 +2,4 @@ flask
|
|||||||
apscheduler
|
apscheduler
|
||||||
requests
|
requests
|
||||||
treelib
|
treelib
|
||||||
|
pypinyin
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user