在文件整理页面增加了移动文件和新建文件夹功能

This commit is contained in:
x1ao4 2025-07-04 14:30:15 +08:00
parent 0f121a2a4a
commit ffe6bfdbaa
4 changed files with 640 additions and 62 deletions

View File

@ -1021,6 +1021,114 @@ def delete_file():
return jsonify(response)
@app.route("/move_file", methods=["POST"])
def move_file():
if not is_login():
return jsonify({"success": False, "message": "未登录"})
# 获取账号索引参数
account_index = int(request.json.get("account_index", 0))
# 验证账号索引
if account_index < 0 or account_index >= len(config_data["cookie"]):
return jsonify({"success": False, "message": "账号索引无效"})
account = Quark(config_data["cookie"][account_index], account_index)
# 获取参数
file_ids = request.json.get("file_ids", [])
target_folder_id = request.json.get("target_folder_id")
if not file_ids:
return jsonify({"success": False, "message": "缺少文件ID列表"})
if not target_folder_id:
return jsonify({"success": False, "message": "缺少目标文件夹ID"})
try:
# 调用夸克网盘的移动API
response = account.move(file_ids, target_folder_id)
if response["code"] == 0:
return jsonify({
"success": True,
"message": f"成功移动 {len(file_ids)} 个文件",
"moved_count": len(file_ids)
})
else:
return jsonify({
"success": False,
"message": response.get("message", "移动失败")
})
except Exception as e:
return jsonify({
"success": False,
"message": f"移动文件时出错: {str(e)}"
})
@app.route("/create_folder", methods=["POST"])
def create_folder():
if not is_login():
return jsonify({"success": False, "message": "未登录"})
# 获取请求参数
data = request.json
parent_folder_id = data.get("parent_folder_id")
folder_name = data.get("folder_name", "新建文件夹")
account_index = int(data.get("account_index", 0))
# 验证参数
if not parent_folder_id:
return jsonify({"success": False, "message": "缺少父目录ID"})
# 验证账号索引
if account_index < 0 or account_index >= len(config_data["cookie"]):
return jsonify({"success": False, "message": "账号索引无效"})
try:
# 初始化夸克网盘客户端
account = Quark(config_data["cookie"][account_index], account_index)
# 调用新建文件夹API
response = account.mkdir_in_folder(parent_folder_id, folder_name)
if response.get("code") == 0:
# 创建成功,返回新文件夹信息
new_folder = response.get("data", {})
return jsonify({
"success": True,
"message": "文件夹创建成功",
"data": {
"fid": new_folder.get("fid"),
"file_name": new_folder.get("file_name", folder_name),
"dir": True,
"size": 0,
"updated_at": new_folder.get("updated_at"),
"include_items": 0
}
})
else:
# 处理特定的错误信息
error_message = response.get("message", "创建文件夹失败")
# 检查是否是同名文件夹冲突
if "同名" in error_message or "已存在" in error_message or "重复" in error_message or "doloading" in error_message:
error_message = "已存在同名文件夹,请修改名称后再试"
return jsonify({
"success": False,
"message": error_message
})
except Exception as e:
logging.error(f">>> 创建文件夹时出错: {str(e)}")
return jsonify({
"success": False,
"message": f"创建文件夹时出错: {str(e)}"
})
# 添加任务接口
@app.route("/api/add_task", methods=["POST"])
def add_task():

View File

@ -804,12 +804,16 @@ main div[v-if="activeTab === 'config'"] .row.title:first-child {
/* 文件夹图标样式 */
.bi-folder {
color: var(--dark-text-color);
font-size: 0.98rem;
font-size: 1.01rem;
position: relative;
top: 0.5px;
}
/* 重置文件夹图标样式 */
.bi-folder-x {
font-size: 0.98rem;
font-size: 1.01rem;
position: relative;
top: 0.5px;
}
/* 恢复图标样式 */
@ -876,7 +880,9 @@ select.form-control {
/* 链接图标样式 */
.bi-link-45deg {
color: var(--dark-text-color);
font-size: 1.15rem;
font-size: 1.2rem;
position: relative;
top: 0.5px;
}
/* 谷歌图标样式 */
@ -2118,7 +2124,7 @@ div.jsoneditor-tree button.jsoneditor-button:focus {
/* 侧边栏菜单项图标样式 */
.sidebar .nav-link .bi-list-ul {
font-size: 1.08rem;
font-size: 1.09rem;
position: relative;
top: 0.5px; /* 向下微调 */
}
@ -4249,6 +4255,13 @@ select.task-filter-select,
font-size: 1.17rem;
}
/* 文件整理页面新建文件夹按钮图标大小 */
.batch-rename-btn .bi-folder-plus {
font-size: 1.01rem;
position: relative;
top: 0px;
}
/* 确保文件整理页面的Plex和AList按钮样式与任务列表一致 */
.batch-rename-btn.btn-outline-plex,
.batch-rename-btn.btn-outline-alist {
@ -4293,6 +4306,8 @@ select.task-filter-select,
filter: brightness(0) invert(1);
}
/* 文件表格中的展开按钮 */
.expand-button {
position: absolute;
@ -4587,6 +4602,11 @@ table.selectable-files .rename-record-btn:hover {
color: #0A42CC !important;
}
/* 文件整理页面移动按钮悬停效果 */
table.selectable-files .move-record-btn:hover {
color: #0A42CC !important;
}
/* 文件整理页面删除按钮悬停效果 */
table.selectable-files .delete-record-btn:hover {
color: #b02a37 !important;
@ -4699,17 +4719,6 @@ table.selectable-records tr td {
/* 文件大小列中展开行的删除按钮特殊处理 */
tr:has(.expanded-text) .file-size-cell .delete-record-btn,
.selectable-records tbody tr:has(.expanded-text) .file-size-cell .delete-record-btn,
#fileSelectModal .table tr:has([style*="white-space: normal"]) .file-size-cell .delete-record-btn,
.selectable-files tr:has([style*="white-space: normal"]) .file-size-cell .delete-record-btn {
top: 8px !important; /* 强制固定位置 */
transform: none !important; /* 确保不使用任何转换 */
left: 32px !important; /* 确保左边距固定,在重命名按钮右侧 */
/* 不设置display保持默认的隐藏状态 */
}
/* 文件大小列中展开行的重命名按钮特殊处理 */
tr:has(.expanded-text) .file-size-cell .rename-record-btn,
.selectable-records tbody tr:has(.expanded-text) .file-size-cell .rename-record-btn,
@ -4721,6 +4730,28 @@ tr:has(.expanded-text) .file-size-cell .rename-record-btn,
/* 不设置display保持默认的隐藏状态 */
}
/* 文件大小列中展开行的移动按钮特殊处理 */
tr:has(.expanded-text) .file-size-cell .move-record-btn,
.selectable-records tbody tr:has(.expanded-text) .file-size-cell .move-record-btn,
#fileSelectModal .table tr:has([style*="white-space: normal"]) .file-size-cell .move-record-btn,
.selectable-files tr:has([style*="white-space: normal"]) .file-size-cell .move-record-btn {
top: 8px !important; /* 强制固定位置 */
transform: none !important; /* 确保不使用任何转换 */
left: 32px !important; /* 确保左边距固定,在重命名按钮右侧 */
/* 不设置display保持默认的隐藏状态 */
}
/* 文件大小列中展开行的删除按钮特殊处理 */
tr:has(.expanded-text) .file-size-cell .delete-record-btn,
.selectable-records tbody tr:has(.expanded-text) .file-size-cell .delete-record-btn,
#fileSelectModal .table tr:has([style*="white-space: normal"]) .file-size-cell .delete-record-btn,
.selectable-files tr:has([style*="white-space: normal"]) .file-size-cell .delete-record-btn {
top: 8px !important; /* 强制固定位置 */
transform: none !important; /* 确保不使用任何转换 */
left: 59px !important; /* 确保左边距固定,在移动按钮右侧 */
/* 不设置display保持默认的隐藏状态 */
}
/* 修复删除按钮位置问题 - 使用更强制的方法 */
.delete-record-btn {
color: #dc3545;
@ -4808,11 +4839,34 @@ tr.selected-record .file-size-cell .delete-record-btn,
/* 文件整理页面的重命名文件按钮图标大小 */
.selectable-files .rename-record-btn .bi-pencil {
font-size: 0.98rem;
font-size: 0.99rem;
position: relative;
left: 0.5px;
}
/* 移动按钮样式 */
.selectable-files .move-record-btn {
color: var(--focus-border-color);
cursor: pointer;
display: none;
align-items: center;
justify-content: center;
width: 24px;
height: 24px;
border-radius: 4px;
position: absolute;
left: 32px; /* 在重命名按钮右侧,调整间距 */
top: 50%;
transform: translateY(-50%);
font-size: 14px;
}
/* 文件整理页面的移动文件按钮图标大小 */
.selectable-files .move-record-btn .bi-arrow-up-right-circle {
font-size: 1.07rem;
position: relative;
}
/* 删除按钮样式调整 */
.selectable-files .delete-record-btn {
color: #dc3545;
@ -4825,7 +4879,7 @@ tr.selected-record .file-size-cell .delete-record-btn,
border-radius: 4px;
position: absolute;
right: auto; /* 移除右对齐 */
left: 32px; /* 在重命名按钮右侧,调整间距 */
left: 59px; /* 在移动按钮右侧,调整间距 */
top: 50%;
transform: translateY(-50%);
font-size: 14px;
@ -4837,9 +4891,11 @@ tr.selected-record .file-size-cell .delete-record-btn,
min-width: 80px;
}
/* 修复:确保在悬停和选中状态下重命名和删除按钮显示 */
/* 修复:确保在悬停和选中状态下重命名、移动和删除按钮显示 */
.selectable-files tr:hover .file-size-cell .rename-record-btn,
.selectable-files tr.selected-file .file-size-cell .rename-record-btn,
.selectable-files tr:hover .file-size-cell .move-record-btn,
.selectable-files tr.selected-file .file-size-cell .move-record-btn,
.selectable-files tr:hover .file-size-cell .delete-record-btn,
.selectable-files tr.selected-file .file-size-cell .delete-record-btn {
display: inline-flex !important;
@ -4892,6 +4948,23 @@ body .selectable-files tr.selected-file .file-size-cell .rename-record-btn {
opacity: 1 !important;
}
/* 确保文件整理页面的移动按钮在悬停和选中状态下始终可见 - 最高优先级 */
body .selectable-files tbody tr:hover .file-size-cell .move-record-btn,
body .selectable-files tr.selected-file .file-size-cell .move-record-btn {
display: inline-flex !important;
visibility: visible !important;
position: absolute !important;
top: 50% !important;
transform: translateY(-50%) !important;
left: 32px !important;
width: 24px !important;
height: 24px !important;
align-items: center !important;
justify-content: center !important;
z-index: 5 !important;
opacity: 1 !important;
}
/* 确保文件整理页面的删除按钮在悬停和选中状态下始终可见 - 最高优先级 */
body .selectable-files tbody tr:hover .file-size-cell .delete-record-btn,
body .selectable-files tr.selected-file .file-size-cell .delete-record-btn {
@ -4900,7 +4973,7 @@ body .selectable-files tr.selected-file .file-size-cell .delete-record-btn {
position: absolute !important;
top: 50% !important;
transform: translateY(-50%) !important;
left: 32px !important;
left: 59px !important;
width: 24px !important;
height: 24px !important;
align-items: center !important;
@ -4926,6 +4999,23 @@ body .selectable-files tr.selected-file:has([style*="white-space: normal"]) .fil
opacity: 1 !important;
}
/* 展开状态下的移动按钮悬停和选中状态 - 最高优先级 */
body .selectable-files tbody tr:hover:has([style*="white-space: normal"]) .file-size-cell .move-record-btn,
body .selectable-files tr.selected-file:has([style*="white-space: normal"]) .file-size-cell .move-record-btn {
display: inline-flex !important;
visibility: visible !important;
position: absolute !important;
top: 8px !important;
transform: none !important;
left: 32px !important;
width: 24px !important;
height: 24px !important;
align-items: center !important;
justify-content: center !important;
z-index: 5 !important;
opacity: 1 !important;
}
/* 展开状态下的删除按钮悬停和选中状态 - 最高优先级 */
body .selectable-files tbody tr:hover:has([style*="white-space: normal"]) .file-size-cell .delete-record-btn,
body .selectable-files tr.selected-file:has([style*="white-space: normal"]) .file-size-cell .delete-record-btn {
@ -4934,7 +5024,7 @@ body .selectable-files tr.selected-file:has([style*="white-space: normal"]) .fil
position: absolute !important;
top: 8px !important;
transform: none !important;
left: 32px !important;
left: 59px !important;
width: 24px !important;
height: 24px !important;
align-items: center !important;
@ -5197,6 +5287,18 @@ body .selectable-files tr.selected-file:has([style*="white-space: normal"]) .fil
color: var(--dark-text-color) !important;
}
/* 移动文件模态框的取消按钮样式 */
#fileSelectModal[data-modal-type="move"] .modal-footer .btn-cancel {
background-color: var(--button-gray-background-color) !important;
border-color: var(--button-gray-background-color) !important;
color: var(--dark-text-color) !important;
}
#fileSelectModal[data-modal-type="move"] .modal-footer .btn-cancel:hover {
background-color: #e0e2e6 !important;
border-color: #e0e2e6 !important;
color: var(--dark-text-color) !important;
}
@media (max-width: 767.98px) {
.file-manager-rule-bar-responsive {
display: flex;

View File

@ -1052,6 +1052,9 @@
</div>
<button type="button" class="btn btn-outline-plex batch-rename-btn" v-if="formData.plugins && formData.plugins.plex && formData.plugins.plex.url && formData.plugins.plex.token && formData.plugins.plex.quark_root_path && formData.button_display.refresh_plex !== 'disabled'" @click="refreshFileManagerPlexLibrary" title="刷新Plex媒体库"><img src="./static/Plex.svg" class="plex-icon"></button>
<button type="button" class="btn btn-outline-alist batch-rename-btn" v-if="formData.plugins && formData.plugins.alist && formData.plugins.alist.url && formData.plugins.alist.token && formData.plugins.alist.storage_id && formData.button_display.refresh_alist !== 'disabled'" @click="refreshFileManagerAlistDirectory" title="刷新AList目录"><img src="https://cdn.jsdelivr.net/gh/alist-org/logo@main/logo.svg" class="alist-icon"></button>
<button type="button" class="btn btn-outline-primary batch-rename-btn" @click="createNewFolder" title="新建文件夹">
<i class="bi bi-folder-plus"></i>
</button>
<button type="button" class="btn btn-outline-primary batch-rename-btn" @click="refreshCurrentFolderCache" title="刷新当前目录缓存">
<i class="bi bi-arrow-clockwise"></i>
</button>
@ -1069,6 +1072,9 @@
</div>
<button type="button" class="btn btn-outline-plex batch-rename-btn" v-if="formData.plugins && formData.plugins.plex && formData.plugins.plex.url && formData.plugins.plex.token && formData.plugins.plex.quark_root_path && formData.button_display.refresh_plex !== 'disabled'" @click="refreshFileManagerPlexLibrary" title="刷新Plex媒体库"><img src="./static/Plex.svg" class="plex-icon"></button>
<button type="button" class="btn btn-outline-alist batch-rename-btn" v-if="formData.plugins && formData.plugins.alist && formData.plugins.alist.url && formData.plugins.alist.token && formData.plugins.alist.storage_id && formData.button_display.refresh_alist !== 'disabled'" @click="refreshFileManagerAlistDirectory" title="刷新AList目录"><img src="https://cdn.jsdelivr.net/gh/alist-org/logo@main/logo.svg" class="alist-icon"></button>
<button type="button" class="btn btn-outline-primary batch-rename-btn" @click="createNewFolder" title="新建文件夹">
<i class="bi bi-folder-plus"></i>
</button>
<button type="button" class="btn btn-outline-primary batch-rename-btn" @click="refreshCurrentFolderCache" title="刷新当前目录缓存">
<i class="bi bi-arrow-clockwise"></i>
</button>
@ -1207,6 +1213,9 @@
<span class="rename-record-btn" @click.stop="startRenameFile(file)" title="重命名文件">
<i class="bi bi-pencil"></i>
</span>
<span class="move-record-btn" @click.stop="startMoveFile(file)" title="移动文件">
<i class="bi bi-arrow-up-right-circle"></i>
</span>
<span class="delete-record-btn" @click.stop="deleteSelectedFilesForManager(file.fid, file.file_name, file.dir)" title="删除文件">
<i class="bi bi-trash3"></i>
</span>
@ -1303,9 +1312,9 @@
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" style="font-weight: 600; font-family: inherit; letter-spacing: normal;">
<span v-if="fileSelect.previewRegex && fileSelect.index >= 0" style="font-weight: 600; font-family: inherit; letter-spacing: normal;">{{ formData.tasklist[fileSelect.index].use_sequence_naming ? '顺序命名预览' : (formData.tasklist[fileSelect.index].use_episode_naming ? '剧集命名预览' : '正则命名预览') }}</span>
<span v-if="fileSelect.previewRegex && fileSelect.index >= 0 && formData.tasklist[fileSelect.index]" style="font-weight: 600; font-family: inherit; letter-spacing: normal;">{{ formData.tasklist[fileSelect.index].use_sequence_naming ? '顺序命名预览' : (formData.tasklist[fileSelect.index].use_episode_naming ? '剧集命名预览' : '正则命名预览') }}</span>
<span v-else-if="fileSelect.previewRegex && fileSelect.index === -1" style="font-weight: 600; font-family: inherit; letter-spacing: normal;">{{ fileManager.use_sequence_naming ? '顺序命名预览' : (fileManager.use_episode_naming ? '剧集命名预览' : '正则命名预览') }}</span>
<span v-else-if="fileSelect.selectDir" style="font-weight: 600; font-family: inherit; letter-spacing: normal;">选择{{fileSelect.selectShare ? '需转存的' : '保存到的'}}文件夹</span>
<span v-else-if="fileSelect.selectDir" style="font-weight: 600; font-family: inherit; letter-spacing: normal;">选择{{fileSelect.moveMode ? '移动到的' : (fileSelect.selectShare ? '需转存的' : '保存到的')}}文件夹</span>
<span v-else style="font-weight: 600; font-family: inherit; letter-spacing: normal;">选择起始文件</span>
<div v-if="modalLoading" class="spinner-border spinner-border-sm m-1" role="status"></div>
</h5>
@ -1329,7 +1338,7 @@
<!-- 文件列表 -->
<div class="mb-3" v-if="fileSelect.previewRegex">
<!-- 任务配置的命名预览 -->
<div v-if="fileSelect.index >= 0">
<div v-if="fileSelect.index >= 0 && formData.tasklist[fileSelect.index]">
<div v-if="formData.tasklist[fileSelect.index].use_sequence_naming">
<div style="margin-bottom: 1px; padding-left: 12px; display: flex; align-items: center;">
<span style="font-weight: 600; font-family: inherit; letter-spacing: normal;">顺序命名表达式:</span><span class="badge badge-info" v-html="formData.tasklist[fileSelect.index].pattern"></span>
@ -1386,7 +1395,7 @@
<template v-if="!fileSelect.previewRegex">
<th scope="col" class="col-size cursor-pointer" style="font-family: inherit; letter-spacing: normal;" @click="sortFileList('size')">大小 <i v-if="fileSelect.sortBy === 'size'" :class="fileSelect.sortOrder === 'asc' ? 'bi bi-arrow-up' : 'bi bi-arrow-down'"></i></th>
<th scope="col" class="col-date cursor-pointer" style="font-family: inherit; letter-spacing: normal;" @click="sortFileList('updated_at')">修改日期 <i v-if="fileSelect.sortBy === 'updated_at'" :class="fileSelect.sortOrder === 'asc' ? 'bi bi-arrow-up' : 'bi bi-arrow-down'"></i></th>
<th scope="col" class="col-action" v-if="!fileSelect.selectShare" style="font-family: inherit; letter-spacing: normal;">操作</th>
<th scope="col" class="col-action" v-if="!fileSelect.selectShare && !fileSelect.moveMode" style="font-family: inherit; letter-spacing: normal;">操作</th>
</template>
</tr>
</thead>
@ -1441,7 +1450,7 @@
<td class="col-size" v-if="file.dir" style="vertical-align: top;">{{ file.include_items }} 项</td>
<td class="col-size" v-else style="vertical-align: top;">{{file.size | size}}</td>
<td class="col-date" style="vertical-align: top;">{{file.updated_at | ts2date}}</td>
<td class="col-action" v-if="!fileSelect.selectShare" style="vertical-align: top;">
<td class="col-action" v-if="!fileSelect.selectShare && !fileSelect.moveMode" style="vertical-align: top;">
<a @click.stop.prevent="deleteSelectedFiles(file.fid, file.file_name, file.dir)" style="cursor: pointer;">删除文件</a>
<a @click.stop.prevent="deleteSelectedFiles(file.fid, file.file_name, file.dir, true)" style="cursor: pointer; margin-left: 10px;">删除文件和记录</a>
</td>
@ -1455,8 +1464,12 @@
<div class="file-selection-info mr-auto" style="color: var(--dark-text-color); font-size: 0.875rem; line-height: 1.5;">
共 {{ fileSelect.fileList.length }} 个项目<span v-if="fileSelect.selectedFiles.length > 0">,已选中 {{ fileSelect.selectedFiles.length }} 个项目</span>
</div>
<button type="button" class="btn btn-primary btn-sm" @click="selectCurrentFolder()">{{fileSelect.selectShare ? '转存当前文件夹' : '保存到当前文件夹'}}</button>
<button type="button" class="btn btn-primary btn-sm" v-if="!fileSelect.selectShare" @click="selectCurrentFolder(true)">保存到当前位置的「<span class="badge badge-light" v-html="formData.tasklist[fileSelect.index].taskname"></span>」文件夹</button>
<button type="button" class="btn btn-primary btn-cancel" @click="$('#fileSelectModal').modal('hide')" v-if="fileSelect.moveMode">取消</button>
<button type="button" class="btn btn-primary btn-sm" @click="selectCurrentFolder()">
<span v-if="fileSelect.moveMode">移动到当前文件夹</span>
<span v-else>{{fileSelect.selectShare ? '转存当前文件夹' : '保存到当前文件夹'}}</span>
</button>
<button type="button" class="btn btn-primary btn-sm" v-if="!fileSelect.selectShare && !fileSelect.moveMode && fileSelect.index !== null && fileSelect.index >= 0 && formData.tasklist[fileSelect.index]" @click="selectCurrentFolder(true)">保存到当前位置的「<span class="badge badge-light" v-html="formData.tasklist[fileSelect.index].taskname"></span>」文件夹</button>
</div>
<div class="modal-footer" v-if="fileSelect.previewRegex && fileSelect.index === -1">
<div class="file-selection-info mr-auto" style="color: var(--dark-text-color); font-size: 0.875rem; line-height: 1.5;">
@ -1572,7 +1585,9 @@
sortOrder: "desc", // 默认排序顺序
selectedFiles: [], // 存储选中的文件ID
lastSelectedFileIndex: -1, // 记录最后选择的文件索引
canUndoRename: false
canUndoRename: false,
moveMode: false, // 是否为移动文件模式
moveFileIds: [] // 要移动的文件ID列表
},
historyParams: {
sortBy: "transfer_time",
@ -1823,6 +1838,9 @@
$('#fileSelectModal').on('hidden.bs.modal', () => {
this.fileSelect.selectedFiles = [];
this.fileSelect.lastSelectedFileIndex = -1;
// 重置移动模式相关参数
this.fileSelect.moveMode = false;
this.fileSelect.moveFileIds = [];
});
// 检查本地存储中的标签页状态
@ -1883,6 +1901,9 @@
$('#fileSelectModal').on('hidden.bs.modal', () => {
this.fileSelect.selectedFiles = [];
this.fileSelect.lastSelectedFileIndex = -1;
// 重置移动模式相关参数
this.fileSelect.moveMode = false;
this.fileSelect.moveFileIds = [];
});
window.addEventListener('beforeunload', this.handleBeforeUnload);
@ -2061,9 +2082,19 @@
// 重置页码并切换到新账号的最后访问目录
this.fileManager.currentPage = 1;
this.loadFileListWithFallback(newAccountLastFolder);
// 如果移动文件模态框正在显示,需要重新加载目录
if ($('#fileSelectModal').hasClass('show')) {
const modalType = document.getElementById('fileSelectModal').getAttribute('data-modal-type');
if (modalType === 'move') {
// 重置路径并重新加载根目录
this.fileSelect.paths = [];
this.getSavepathDetail(0);
}
}
},
refreshCurrentFolderCache() {
refreshCurrentFolderCache(retryCount = 0) {
// 刷新当前目录的缓存,强制重新请求最新的文件列表
// 调用后端接口,添加强制刷新参数
const params = {
@ -2073,7 +2104,8 @@
page_size: this.fileManager.pageSize,
page: this.fileManager.currentPage,
account_index: this.fileManager.selectedAccountIndex,
force_refresh: true // 强制刷新参数
force_refresh: true, // 强制刷新参数
timestamp: Date.now() // 添加时间戳避免缓存
};
axios.get('/file_list', { params })
@ -2085,16 +2117,163 @@
this.fileManager.paths = response.data.data.paths || [];
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 => {
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('创建文件夹失败');
});
},
// 添加一个检查分享链接状态的方法
@ -3115,9 +3294,9 @@
// 根据模态框类型决定使用哪个账号
// 任务配置相关的模态框始终使用主账号索引0
// 文件整理页面的预览模态框使用选中的账号
// 文件整理页面的预览模态框和移动文件模态框使用选中的账号
const modalType = document.getElementById('fileSelectModal').getAttribute('data-modal-type');
const accountIndex = (modalType === 'preview-filemanager') ? this.fileManager.selectedAccountIndex : 0;
const accountIndex = (modalType === 'preview-filemanager' || modalType === 'move') ? this.fileManager.selectedAccountIndex : 0;
// 添加账号索引参数
if (typeof params === 'object' && params !== null) {
@ -3180,21 +3359,43 @@
},
getShareDetail(retryCount = 0, maxRetries = 1) {
this.modalLoading = true;
// 检查index是否有效如果无效则使用默认值
let regexConfig = {};
if (this.fileSelect.index !== null && this.fileSelect.index >= 0 && this.formData.tasklist[this.fileSelect.index]) {
const task = this.formData.tasklist[this.fileSelect.index];
regexConfig = {
pattern: task.pattern,
replace: task.replace,
taskname: task.taskname,
filterwords: task.filterwords,
magic_regex: this.formData.magic_regex,
use_sequence_naming: task.use_sequence_naming,
sequence_naming: task.sequence_naming,
use_episode_naming: task.use_episode_naming,
episode_naming: task.episode_naming,
episode_patterns: this.formData.episode_patterns
};
} else {
// 使用默认配置
regexConfig = {
pattern: '',
replace: '',
taskname: '',
filterwords: '',
magic_regex: this.formData.magic_regex,
use_sequence_naming: false,
sequence_naming: '',
use_episode_naming: false,
episode_naming: '',
episode_patterns: this.formData.episode_patterns
};
}
axios.post('/get_share_detail', {
shareurl: this.fileSelect.shareurl,
stoken: this.fileSelect.stoken,
regex: {
pattern: this.formData.tasklist[this.fileSelect.index].pattern,
replace: this.formData.tasklist[this.fileSelect.index].replace,
taskname: this.formData.tasklist[this.fileSelect.index].taskname,
filterwords: this.formData.tasklist[this.fileSelect.index].filterwords,
magic_regex: this.formData.magic_regex,
use_sequence_naming: this.formData.tasklist[this.fileSelect.index].use_sequence_naming,
sequence_naming: this.formData.tasklist[this.fileSelect.index].sequence_naming,
use_episode_naming: this.formData.tasklist[this.fileSelect.index].use_episode_naming,
episode_naming: this.formData.tasklist[this.fileSelect.index].episode_naming,
episode_patterns: this.formData.episode_patterns
}
regex: regexConfig
}).then(response => {
if (response.data.success) {
this.fileSelect.fileList = response.data.data.list;
@ -3324,24 +3525,122 @@
}
},
selectCurrentFolder(addTaskname = false) {
if (this.fileSelect.selectShare) {
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", "该分享已被删除,无法访问");
if (this.fileSelect.moveMode) {
// 移动文件模式
this.moveFilesToCurrentFolder();
} else if (this.fileSelect.selectShare) {
// 检查index是否有效
if (this.fileSelect.index !== null && this.fileSelect.index >= 0 && this.formData.tasklist[this.fileSelect.index]) {
this.formData.tasklist[this.fileSelect.index].shareurl_ban = undefined;
// 如果是分享文件夹且文件列表为空则设置shareurl_ban
if (!this.fileSelect.fileList || this.fileSelect.fileList.length === 0) {
this.$set(this.formData.tasklist[this.fileSelect.index], "shareurl_ban", "该分享已被删除,无法访问");
}
this.formData.tasklist[this.fileSelect.index].shareurl = this.fileSelect.shareurl;
}
this.formData.tasklist[this.fileSelect.index].shareurl = this.fileSelect.shareurl;
} else {
// 去掉前导斜杠,避免双斜杠问题
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
// 检查index是否有效
if (this.fileSelect.index !== null && this.fileSelect.index >= 0 && this.formData.tasklist[this.fileSelect.index]) {
// 去掉前导斜杠,避免双斜杠问题
this.formData.tasklist[this.fileSelect.index].savepath = this.fileSelect.paths.map(item => item.name).join("/");
if (addTaskname) {
this.formData.tasklist[this.fileSelect.index].savepath += "/" + this.formData.tasklist[this.fileSelect.index].taskname
}
}
}
$('#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) {
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')
},
getShareurl(shareurl, path = {}) {
@ -5031,6 +5330,39 @@
}
});
},
// 开始移动文件
startMoveFile(file) {
// 如果是文件夹,不允许移动
if (file.dir) {
this.showToast('暂不支持移动文件夹');
return;
}
// 设置移动模式相关参数
this.fileSelect.moveMode = true;
this.fileSelect.moveFileIds = this.fileManager.selectedFiles.length > 0 && this.fileManager.selectedFiles.includes(file.fid)
? this.fileManager.selectedFiles
: [file.fid];
this.fileSelect.selectShare = false;
this.fileSelect.selectDir = true;
this.fileSelect.previewRegex = false;
this.fileSelect.error = undefined;
this.fileSelect.fileList = [];
this.fileSelect.paths = [];
this.fileSelect.index = -1;
// 重置排序状态为默认值 - 选择移动目标文件夹模态框默认修改时间倒序
this.fileSelect.sortBy = "updated_at";
this.fileSelect.sortOrder = "desc";
// 设置模态框类型为move移动目标文件夹
document.getElementById('fileSelectModal').setAttribute('data-modal-type', 'move');
$('#fileSelectModal').modal('show');
// 加载根目录
this.getSavepathDetail(0);
},
// 保存重命名
saveRenameFile(file) {
if (!file._editing) return;
@ -5534,6 +5866,9 @@
$('#fileSelectModal').on('hidden.bs.modal', () => {
this.fileSelect.selectedFiles = [];
this.fileSelect.lastSelectedFileIndex = -1;
// 重置移动模式相关参数
this.fileSelect.moveMode = false;
this.fileSelect.moveFileIds = [];
});
// 检查本地存储中的标签页状态
@ -5591,6 +5926,9 @@
$('#fileSelectModal').on('hidden.bs.modal', () => {
this.fileSelect.selectedFiles = [];
this.fileSelect.lastSelectedFileIndex = -1;
// 重置移动模式相关参数
this.fileSelect.moveMode = false;
this.fileSelect.moveFileIds = [];
});
window.addEventListener('beforeunload', this.handleBeforeUnload);

View File

@ -1121,6 +1121,21 @@ class Quark:
).json()
return response
def mkdir_in_folder(self, parent_fid, folder_name):
"""在指定父目录下创建新文件夹"""
url = f"{self.BASE_URL}/1/clouddrive/file"
querystring = {"pr": "ucpro", "fr": "pc", "uc_param_str": ""}
payload = {
"pdir_fid": parent_fid,
"file_name": folder_name,
"dir_path": "",
"dir_init_lock": False,
}
response = self._send_request(
"POST", url, json=payload, params=querystring
).json()
return response
def rename(self, fid, file_name):
url = f"{self.BASE_URL}/1/clouddrive/file/rename"
querystring = {"pr": "ucpro", "fr": "pc", "uc_param_str": ""}
@ -1139,6 +1154,21 @@ class Quark:
).json()
return response
def move(self, filelist, to_pdir_fid):
"""移动文件到指定目录"""
url = f"{self.BASE_URL}/1/clouddrive/file/move"
querystring = {"pr": "ucpro", "fr": "pc", "uc_param_str": ""}
payload = {
"action_type": 2,
"filelist": filelist,
"to_pdir_fid": to_pdir_fid,
"exclude_fids": []
}
response = self._send_request(
"POST", url, json=payload, params=querystring
).json()
return response
def recycle_list(self, page=1, size=30):
url = f"{self.BASE_URL}/1/clouddrive/file/recycle/list"
querystring = {