From ad86a7c8a8aa16a11f4efb50f218f793a01c22b3 Mon Sep 17 00:00:00 2001 From: x1ao4 Date: Fri, 23 May 2025 22:55:10 +0800 Subject: [PATCH 01/11] =?UTF-8?q?=E4=BC=98=E5=8C=96=E8=BD=AC=E5=AD=98?= =?UTF-8?q?=E8=AE=B0=E5=BD=95=E9=A1=B5=E9=9D=A2=E7=9A=84=E6=98=BE=E7=A4=BA?= =?UTF-8?q?=E6=95=88=E6=9E=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/static/css/main.css | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/app/static/css/main.css b/app/static/css/main.css index f4a70c1..2b9f8fc 100644 --- a/app/static/css/main.css +++ b/app/static/css/main.css @@ -1120,7 +1120,7 @@ textarea.form-control { border-bottom-right-radius: 0; } -/* --------------- 扩展按钮样式 --------------- */ +/* --------------- 转存记录展开按钮样式 --------------- */ .expand-button { position: absolute; right: 5px; @@ -1180,8 +1180,8 @@ textarea.form-control { vertical-align: middle; border-bottom: 1px solid var(--border-color); /* 修改底部边框为1px */ border-top: 1px solid var(--border-color); /* 添加上边框线 */ - padding-top: 8px; /* 增加上内边距 */ - padding-bottom: 8px; /* 增加下内边距 */ + padding-top: 8.5px; /* 增加上内边距 */ + padding-bottom: 8.5px; /* 增加下内边距 */ padding-left: 9px !important; /* 表头左内边距,与按钮一致 */ padding-right: 9px !important; /* 表头右内边距,与按钮一致 */ background-color: var(--button-gray-background-color); /* 表头背景色 */ @@ -1206,6 +1206,7 @@ textarea.form-control { padding-left: 9px !important; /* 单元格左内边距,与按钮一致 */ padding-right: 9px !important; /* 单元格右内边距,与按钮一致 */ border-bottom: 1px solid var(--border-color); /* 单元格分割线颜色 */ + transform: translateY(0.5px); /* 文本下移0.5px,不影响元素实际高度 */ } /* 表格行悬停样式 */ @@ -3774,7 +3775,13 @@ input.no-spinner { background-color: var(--button-gray-background-color) !important; } -/* 删除按钮样式 */ +/* 文件大小值的文本位置调整 */ +.file-size-cell .file-size-value { + transform: translateY(-1px); /* 文本下移 */ + display: inline-block; /* 确保transform生效 */ +} + +/* 转存记录删除按钮样式 */ .delete-record-btn { color: #dc3545; cursor: pointer; @@ -3786,6 +3793,8 @@ input.no-spinner { border-radius: 4px; transition: background-color 0.2s ease; visibility: hidden; /* 默认隐藏 */ + position: relative; /* 添加相对定位 */ + top: -0.5px; /* 上移 */ } /* 删除按钮图标大小 */ From 18005f7682f79449f7b39b4a8ef11f2e4bdb1a3a Mon Sep 17 00:00:00 2001 From: x1ao4 Date: Sat, 24 May 2025 01:09:44 +0800 Subject: [PATCH 02/11] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=A8=A1=E6=80=81?= =?UTF-8?q?=E6=A1=86=E9=A1=B5=E9=9D=A2=E7=9A=84=E6=98=BE=E7=A4=BA=E6=95=88?= =?UTF-8?q?=E6=9E=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/static/css/main.css | 49 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 43 insertions(+), 6 deletions(-) diff --git a/app/static/css/main.css b/app/static/css/main.css index 2b9f8fc..3c147cd 100644 --- a/app/static/css/main.css +++ b/app/static/css/main.css @@ -1274,8 +1274,8 @@ button.close:focus, align-items: center; border-width: 1px; border-style: solid; - height: 35.5px; - min-height: 35.5px; + height: 36px; + min-height: 36px; } /* 任务列表中的警告框样式 */ @@ -1500,7 +1500,7 @@ button.close:focus, background-color: var(--button-gray-background-color); border-radius: 0px; font-size: 0.85rem; - padding: 6px 12px; + padding: 6.5px 12px 6px 12px; /* 上右下左内边距 */ margin-bottom: 8px; border-top: 1px solid var(--border-color); border-bottom: 1px solid var(--border-color); @@ -1592,7 +1592,7 @@ button.close:focus, border-bottom: 1px solid var(--border-color); color: var(--dark-text-color); font-weight: 600; - padding: 6.5px 12.5px !important; /* 表头上下边距,左右边距 */ + padding: 7px 12.5px 6.5px 12.5px !important; /* 表头上右下左内边距 */ font-size: 0.85rem; white-space: nowrap; position: sticky; @@ -1656,7 +1656,7 @@ button.close:focus, } #fileSelectModal .table td { - padding: 6px 12px !important; /* 单元格上下边距,左右边距 */ + padding: 5.5px 12.5px 7px 12.5px !important; /* 单元格上右下左内边距 */ vertical-align: middle; border-bottom: 1px solid var(--border-color); color: var(--dark-text-color); @@ -1686,12 +1686,16 @@ button.close:focus, color: #ffc107; font-size: 0.9rem; margin-right: 5px; + position: relative; + top: 1px; /* 负值向上移动,正值向下移动 */ } #fileSelectModal .bi-file-earmark { color: var(--dark-text-color); font-size: 0.9rem; margin-right: 5px; + position: relative; + top: 0px !important; /* 负值向上移动,正值向下移动 */ } /* 弹窗删除链接样式 */ @@ -3666,7 +3670,7 @@ input::-moz-list-button { #fileSelectModal .expand-button { position: absolute; right: 5px; - top: 8px; /* 固定高度,不再使用百分比定位 */ + top: 7.5px; /* 固定高度,不再使用百分比定位 */ transform: none; /* 移除垂直居中转换 */ cursor: pointer; opacity: 0; @@ -3862,3 +3866,36 @@ tr.selected-record .file-size-cell .delete-record-btn, table.selectable-records .expand-button:hover { background-color: #fff !important; /* 保持展开按钮原有的白色背景 */ } + +/* 模态框表格单元格文本垂直对齐调整 */ +#fileSelectModal .table td { + transform: translateY(0.5px); /* 文本下移0.5px,不影响元素实际高度 */ +} + +/* 确保文件大小、修改日期和操作列的文本位置与文件名一致 */ +#fileSelectModal .table td:not(.col-filename) { + transform: translateY(1.5px); /* 非文件名列文本下移1.5px */ +} + +/* 特别调整红色"×"符号的位置 */ +#fileSelectModal .table td.col-rename.text-danger div:not(.expand-button) { + position: relative; + top: -1px; /* 将"×"标记上移1px */ +} + +/* 文件名列已经通过图标微调过,保持原样或细微调整 */ +#fileSelectModal .bi-folder-fill { + color: #ffc107; + font-size: 0.9rem; + margin-right: 5px; + position: relative; + top: 1px; /* 负值向上移动,正值向下移动 */ +} + +#fileSelectModal .bi-file-earmark { + color: var(--dark-text-color); + font-size: 0.9rem; + margin-right: 5px; + position: relative; + top: 0px; /* 负值向上移动,正值向下移动 */ +} From 7f8ff6279c1c25a644156e1c6554a0893c459f0a Mon Sep 17 00:00:00 2001 From: x1ao4 Date: Sat, 24 May 2025 19:31:08 +0800 Subject: [PATCH 03/11] =?UTF-8?q?=E8=A7=84=E8=8C=83=E6=97=A5=E5=BF=97?= =?UTF-8?q?=E8=BE=93=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- quark_auto_save.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/quark_auto_save.py b/quark_auto_save.py index 4bfb170..6c9e28a 100644 --- a/quark_auto_save.py +++ b/quark_auto_save.py @@ -819,7 +819,7 @@ class Quark: fids += response["data"] file_paths = file_paths[50:] else: - print(f"获取目录ID:失败, {response['message']}") + print(f"获取目录ID: 失败, {response['message']}") break if len(file_paths) == 0: break @@ -1286,8 +1286,8 @@ class Quark: def do_save_task(self, task): # 判断资源失效记录 if task.get("shareurl_ban"): - print(f"分享资源已失效:{task['shareurl_ban']}") - add_notify(f"❗《{task['taskname']}》分享资源已失效:{task['shareurl_ban']}\n") + print(f"分享资源已失效: {task['shareurl_ban']}") + add_notify(f"❗《{task['taskname']}》分享资源已失效: {task['shareurl_ban']}\n") return # 标准化保存路径,去掉可能存在的首位斜杠,然后重新添加 @@ -1304,8 +1304,8 @@ class Quark: is_sharing, stoken = self.get_stoken(pwd_id, passcode) if not is_sharing: task["shareurl_ban"] = stoken - print(f"分享详情获取失败:{stoken}") - add_notify(f"❗《{task['taskname']}》分享详情获取失败:{stoken}\n") + print(f"分享详情获取失败: {stoken}") + add_notify(f"❗《{task['taskname']}》分享详情获取失败: {stoken}\n") return share_detail = self.get_detail(pwd_id, stoken, pdir_fid, _fetch_share=1) # 获取保存路径fid @@ -1393,7 +1393,7 @@ class Quark: if not share_file_list: if subdir_path == "": task["shareurl_ban"] = "分享为空,文件已被分享者删除" - add_notify(f"❌《{task['taskname']}》:{task['shareurl_ban']}\n") + add_notify(f"❌《{task['taskname']}》: {task['shareurl_ban']}\n") return tree elif ( len(share_file_list) == 1 @@ -1445,7 +1445,7 @@ class Quark: if task.get("use_sequence_naming") or task.get("use_episode_naming"): # 计算剩余的实际可用文件数(排除文件夹) remaining_usable_count = len([f for f in share_file_list if not f.get("dir", False)]) - print(f"📑 应用过滤词: {task['filterwords']},剩余{remaining_usable_count}个项目") + print(f"📑 应用过滤词: {task['filterwords']},剩余 {remaining_usable_count} 个项目") else: # 正则模式下,需要先检查哪些文件/文件夹会被实际转存 pattern, replace = "", "" @@ -1479,7 +1479,7 @@ class Quark: print(f"⚠️ 计算可处理项目时出错: {str(e)}") remaining_count = len([f for f in share_file_list if re.search(pattern, f["file_name"])]) - print(f"📑 应用过滤词: {task['filterwords']},剩余{remaining_count}个项目") + print(f"📑 应用过滤词: {task['filterwords']},剩余 {remaining_count} 个项目") print() # 获取目标目录文件列表 @@ -2215,7 +2215,7 @@ class Quark: else: err_msg = save_file_return["message"] if err_msg: - add_notify(f"❌《{task['taskname']}》转存失败:{err_msg}\n") + add_notify(f"❌《{task['taskname']}》转存失败: {err_msg}\n") else: # 没有新文件需要转存 if not subdir_path: # 只在顶层(非子目录)打印一次消息 @@ -2369,7 +2369,7 @@ class Quark: # 移除直接打印的部分,由do_save负责打印 # print(rename_log) except Exception as e: - rename_log = f"重命名出错: {dir_file['file_name']} → {save_name},错误:{str(e)}" + rename_log = f"重命名出错: {dir_file['file_name']} → {save_name},错误: {str(e)}" rename_logs.append(rename_log) # 移除直接打印的部分,由do_save负责打印 # print(rename_log) @@ -2455,7 +2455,7 @@ class Quark: # 获取分享详情 is_sharing, stoken = self.get_stoken(pwd_id, passcode) if not is_sharing: - print(f"分享详情获取失败:{stoken}") + print(f"分享详情获取失败: {stoken}") return False, [] # 获取分享文件列表 @@ -2758,11 +2758,11 @@ class Quark: return True, rename_logs else: err_msg = query_task_return["message"] - add_notify(f"❌《{task['taskname']}》转存失败:{err_msg}\n") + add_notify(f"❌《{task['taskname']}》转存失败: {err_msg}\n") return False, [] else: print(f"❌ 保存文件失败: {save_file_return['message']}") - add_notify(f"❌《{task['taskname']}》转存失败:{save_file_return['message']}\n") + add_notify(f"❌《{task['taskname']}》转存失败: {save_file_return['message']}\n") return False, [] else: # print("没有需要保存的新文件") @@ -2950,7 +2950,7 @@ class Quark: if new_name != orig_name: rename_operations.append((dir_file, new_name)) except Exception as e: - print(f"正则替换出错: {dir_file['file_name']},错误:{str(e)}") + print(f"正则替换出错: {dir_file['file_name']},错误: {str(e)}") # 按原始文件名字母顺序排序,使重命名操作有序进行 # rename_operations.sort(key=lambda x: x[0]["file_name"]) @@ -2989,7 +2989,7 @@ class Quark: rename_logs.append(error_log) except Exception as e: # 收集错误日志但不打印 - error_log = f"重命名出错: {dir_file['file_name']} → {new_name},错误:{str(e)}" + error_log = f"重命名出错: {dir_file['file_name']} → {new_name},错误: {str(e)}" rename_logs.append(error_log) else: # 重名警告但不打印 From 831a5cfa24563b40782e155ce6fc624304380d15 Mon Sep 17 00:00:00 2001 From: x1ao4 Date: Sun, 25 May 2025 00:22:56 +0800 Subject: [PATCH 04/11] =?UTF-8?q?=E4=B8=BA=E9=80=89=E6=8B=A9=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E5=A4=B9=E7=95=8C=E9=9D=A2=E5=A2=9E=E5=8A=A0=E9=A1=B9?= =?UTF-8?q?=E7=9B=AE=E6=95=B0=E9=87=8F=E6=98=BE=E7=A4=BA=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=EF=BC=8C=E4=B8=BA=E5=88=A0=E9=99=A4=E6=96=87=E4=BB=B6=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E5=A2=9E=E5=8A=A0=E5=A4=9A=E9=80=89=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/static/css/main.css | 20 +++- app/templates/index.html | 228 ++++++++++++++++++++++++++++++++++++--- 2 files changed, 230 insertions(+), 18 deletions(-) diff --git a/app/static/css/main.css b/app/static/css/main.css index 3c147cd..89c394a 100644 --- a/app/static/css/main.css +++ b/app/static/css/main.css @@ -1656,7 +1656,7 @@ button.close:focus, } #fileSelectModal .table td { - padding: 5.5px 12.5px 7px 12.5px !important; /* 单元格上右下左内边距 */ + padding: 5.5px 12.5px 7px 12px !important; /* 单元格上右下左内边距 */ vertical-align: middle; border-bottom: 1px solid var(--border-color); color: var(--dark-text-color); @@ -1722,8 +1722,14 @@ button.close:focus, padding-right: 12px; /* 右边距 */ } +/* 添加文件选择模态框左下角文件信息文本的左边距样式 */ +#fileSelectModal .modal-footer .file-selection-info { + margin-left: 0px; /* 与表格左边距保持一致 */ + font-size: 0.85rem !important; /* 覆盖内联样式 */ +} + #fileSelectModal .modal-footer span { - font-size: 0.9rem; + font-size: 0.85rem; color: var(--dark-text-color); margin-right: auto; } @@ -3899,3 +3905,13 @@ table.selectable-records .expand-button:hover { position: relative; top: 0px; /* 负值向上移动,正值向下移动 */ } + +/* 添加选中文件的样式 */ +.selected-file { + background-color: var(--button-gray-background-color); +} + +/* 确保文件选择模态框中的表格行在选中状态下保持可见 */ +#fileSelectModal .table tr.selected-file:hover { + background-color: var(--button-gray-background-color); +} diff --git a/app/templates/index.html b/app/templates/index.html index fe2363b..7c54034 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -912,7 +912,14 @@ - +
{{ file.include_items }} 项 {{file.size | size}} {{file.updated_at | ts2date}} - 删除 + + 删除 + @@ -953,6 +962,9 @@
@@ -1046,7 +1058,9 @@ selectShare: true, previewRegex: false, sortBy: "updated_at", // 默认排序字段 - sortOrder: "desc" // 默认排序顺序 + sortOrder: "desc", // 默认排序顺序 + selectedFiles: [], // 存储选中的文件ID + lastSelectedFileIndex: -1 // 记录最后选择的文件索引 }, historyParams: { sortBy: "transfer_time", @@ -1203,13 +1217,20 @@ this.checkNewVersion(); this.fetchUserInfo(); // 获取用户信息 - // 从本地存储中恢复之前的标签页状态 + // 添加点击事件监听 + document.addEventListener('click', this.handleOutsideClick); + document.addEventListener('click', this.handleModalOutsideClick); + + // 添加模态框关闭事件监听 + $('#fileSelectModal').on('hidden.bs.modal', () => { + this.fileSelect.selectedFiles = []; + this.fileSelect.lastSelectedFileIndex = -1; + }); + + // 检查本地存储中的标签页状态 const savedTab = localStorage.getItem('quarkAutoSave_activeTab'); if (savedTab) { this.activeTab = savedTab; - } else { - // 默认显示任务列表页面 - this.activeTab = 'tasklist'; } // 从本地存储中恢复侧边栏折叠状态 @@ -1251,6 +1272,15 @@ // 添加点击事件监听器,用于在点击表格外区域时取消选择记录 document.addEventListener('click', this.handleOutsideClick); + // 添加点击事件监听器,用于在点击模态框表格外区域时取消选择文件 + document.addEventListener('click', this.handleModalOutsideClick); + + // 添加模态框关闭事件监听,清空选中文件列表 + $('#fileSelectModal').on('hidden.bs.modal', () => { + this.fileSelect.selectedFiles = []; + this.fileSelect.lastSelectedFileIndex = -1; + }); + window.addEventListener('beforeunload', this.handleBeforeUnload); // 监听模态框显示事件,检查滚动条状态 @@ -2261,17 +2291,34 @@ } }, deleteFile(fid, fname, isDir) { - if (fid != "" && confirm(`确定要删除${isDir ? '目录' : '文件'} [${fname}] 吗?`)) - axios.post('/delete_file', { - fid: fid - }).then(response => { - if (response.data.code == 0) { - this.fileSelect.fileList = this.fileSelect.fileList.filter(item => item.fid != fid); + if (!confirm(`确定要删除此项目吗?`)) { + return; + } + + axios.post('/delete_file', { fid: fid }) + .then(response => { + if (response.data.code === 0) { + // 从列表中移除文件 + this.fileSelect.fileList = this.fileSelect.fileList.filter(item => item.fid !== fid); + + // 如果文件在选中列表中,也从选中列表中移除 + if (this.fileSelect.selectedFiles.includes(fid)) { + this.fileSelect.selectedFiles = this.fileSelect.selectedFiles.filter(id => id !== fid); + + // 如果选中列表为空,重置最后选择的索引 + if (this.fileSelect.selectedFiles.length === 0) { + this.fileSelect.lastSelectedFileIndex = -1; + } + } + + this.showToast('成功删除 1 个项目'); } else { - alert('删除失败:' + response.data.message); + alert('删除失败: ' + response.data.message); } - }).catch(error => { - // 错误处理 + }) + .catch(error => { + console.error('删除项目出错:', error); + alert('删除项目出错: ' + (error.response?.data?.message || error.message)); }); }, getSavepathDetail(params = 0) { @@ -3288,6 +3335,155 @@ this.lastSelectedRecordIndex = -1; } }, + selectFileItem(event, fileId) { + // 如果是在预览模式或选择分享模式,不允许选择 + if (this.fileSelect.previewRegex || this.fileSelect.selectShare) return; + + // 获取当前文件的索引 + const currentIndex = this.fileSelect.fileList.findIndex(file => file.fid === fileId); + if (currentIndex === -1) return; + + // 如果是Shift+点击,选择范围 + if (event.shiftKey && this.fileSelect.selectedFiles.length > 0) { + // 找出所有已选中文件的索引 + const selectedIndices = this.fileSelect.selectedFiles.map(id => + this.fileSelect.fileList.findIndex(file => file.fid === id) + ).filter(index => index !== -1); // 过滤掉未找到的文件 + + if (selectedIndices.length > 0) { + // 找出已选中文件中最靠前的索引 + const earliestSelectedIndex = Math.min(...selectedIndices); + // 确定最终的选择范围 + const startIndex = Math.min(earliestSelectedIndex, currentIndex); + const endIndex = Math.max(earliestSelectedIndex, currentIndex); + + // 获取范围内所有文件的ID(排除文件夹) + this.fileSelect.selectedFiles = this.fileSelect.fileList + .slice(startIndex, endIndex + 1) + .filter(file => !file.dir) // 只选择文件,不选择文件夹 + .map(file => file.fid); + } else { + // 如果没有有效的选中文件(可能是由于列表刷新),则只选择当前文件 + const file = this.fileSelect.fileList[currentIndex]; + if (!file.dir) { // 不选择文件夹 + this.fileSelect.selectedFiles = [fileId]; + } + } + } + // 如果是Ctrl/Cmd+点击,切换单个文件选择状态 + else if (event.ctrlKey || event.metaKey) { + const file = this.fileSelect.fileList[currentIndex]; + if (file.dir) return; // 不允许选择文件夹 + + if (this.fileSelect.selectedFiles.includes(fileId)) { + this.fileSelect.selectedFiles = this.fileSelect.selectedFiles.filter(id => id !== fileId); + } else { + this.fileSelect.selectedFiles.push(fileId); + } + } + // 普通点击,清除当前选择并选择当前文件 + else { + const file = this.fileSelect.fileList[currentIndex]; + if (file.dir) return; // 不允许选择文件夹 + + if (this.fileSelect.selectedFiles.length === 1 && this.fileSelect.selectedFiles.includes(fileId)) { + this.fileSelect.selectedFiles = []; + } else { + this.fileSelect.selectedFiles = [fileId]; + } + } + + // 更新最后选择的文件索引,只有在有选择文件时才更新 + if (this.fileSelect.selectedFiles.length > 0) { + this.fileSelect.lastSelectedFileIndex = currentIndex; + } else { + this.fileSelect.lastSelectedFileIndex = -1; + } + + // 阻止事件冒泡 + event.stopPropagation(); + }, + deleteSelectedFiles(clickedFid, clickedFname, isDir) { + // 如果是文件夹或者没有选中的文件,则按原来的方式删除单个文件 + if (isDir || this.fileSelect.selectedFiles.length === 0) { + this.deleteFile(clickedFid, clickedFname, isDir); + return; + } + + // 如果点击的文件不在选中列表中,也按原来的方式删除单个文件 + if (!this.fileSelect.selectedFiles.includes(clickedFid)) { + this.deleteFile(clickedFid, clickedFname, isDir); + return; + } + + // 多选删除 + const selectedCount = this.fileSelect.selectedFiles.length; + + // 根据选中数量使用不同的确认提示 + let confirmMessage = selectedCount === 1 + ? `确定要删除此项目吗?` + : `确定要删除选中的 ${selectedCount} 个项目吗?`; + + if (confirm(confirmMessage)) { + // 创建一个Promise数组来处理所有删除请求 + const deletePromises = this.fileSelect.selectedFiles.map(fid => { + return axios.post('/delete_file', { fid: fid }) + .then(response => { + return { fid: fid, success: response.data.code === 0 }; + }) + .catch(error => { + return { fid: fid, success: false }; + }); + }); + + // 等待所有删除请求完成 + Promise.all(deletePromises) + .then(results => { + // 统计成功和失败的数量 + const successCount = results.filter(r => r.success).length; + const failCount = results.length - successCount; + + // 从文件列表中移除成功删除的文件 + const successfullyDeletedFids = results.filter(r => r.success).map(r => r.fid); + this.fileSelect.fileList = this.fileSelect.fileList.filter(item => !successfullyDeletedFids.includes(item.fid)); + + // 清空选中文件列表 + this.fileSelect.selectedFiles = []; + this.fileSelect.lastSelectedFileIndex = -1; + + // 显示结果 + if (failCount > 0) { + alert(`成功删除 ${successCount} 个项目,${failCount} 个项目删除失败`); + } else { + this.showToast(`成功删除 ${successCount} 个项目`); + } + }); + } + }, + handleModalOutsideClick(event) { + // 如果当前不是文件选择模式或者没有选中的文件,则不处理 + if (this.fileSelect.previewRegex || this.fileSelect.selectShare || this.fileSelect.selectedFiles.length === 0) { + return; + } + + // 检查点击是否在表格内 + const tableElement = document.querySelector('#fileSelectModal .table'); + + // 如果点击不在表格内,则清除选择 + if (tableElement && !tableElement.contains(event.target)) { + this.fileSelect.selectedFiles = []; + this.fileSelect.lastSelectedFileIndex = -1; + } + }, + preventTextSelection(event, isDir) { + // 如果是文件夹,不阻止默认行为 + if (isDir) return; + + // 如果是Shift点击或Ctrl/Cmd点击,阻止文本选择 + if (event.shiftKey || event.ctrlKey || event.metaKey) { + event.preventDefault(); + } + }, } }); From 5a5fa4cdeb3bdd0c6c0ecee388b590d3906b9bae Mon Sep 17 00:00:00 2001 From: x1ao4 Date: Sun, 25 May 2025 21:14:04 +0800 Subject: [PATCH 05/11] =?UTF-8?q?=E4=B8=BA=E6=95=B0=E6=8D=AE=E5=BA=93?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BF=9D=E5=AD=98=E8=B7=AF=E5=BE=84=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E7=9A=84=E5=AD=98=E5=82=A8=E5=8A=9F=E8=83=BD=EF=BC=8C?= =?UTF-8?q?=E4=B8=BA=E9=80=89=E6=8B=A9=E4=BF=9D=E5=AD=98=E5=88=B0=E7=9A=84?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E5=A4=B9=E9=A1=B5=E9=9D=A2=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=96=87=E4=BB=B6=E5=92=8C=E8=AE=B0=E5=BD=95?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/run.py | 49 +++++++++++++++++++++++ app/sdk/db.py | 71 ++++++++++++++++++++++++++------- app/static/css/main.css | 13 ++++--- app/templates/index.html | 84 ++++++++++++++++++++++++++++++++-------- quark_auto_save.py | 39 +++++++++++++++++-- 5 files changed, 218 insertions(+), 38 deletions(-) diff --git a/app/run.py b/app/run.py index 596bbd0..2f637b2 100644 --- a/app/run.py +++ b/app/run.py @@ -650,6 +650,55 @@ def delete_file(): account = Quark(config_data["cookie"][0], 0) if fid := request.json.get("fid"): response = account.delete([fid]) + + # 处理delete_records参数 + if request.json.get("delete_records") and response.get("code") == 0: + try: + # 初始化数据库 + db = RecordDB() + + # 获取save_path参数 + save_path = request.json.get("save_path", "") + + # 如果没有提供save_path,则不删除任何记录 + if not save_path: + response["deleted_records"] = 0 + # logging.info(f">>> 删除文件 {fid} 但未提供save_path,不删除任何记录") + return jsonify(response) + + # 查询与该文件ID和save_path相关的所有记录 + cursor = db.conn.cursor() + + # 使用file_id和save_path进行精确匹配 + cursor.execute("SELECT id FROM transfer_records WHERE file_id = ? AND save_path = ?", (fid, save_path)) + record_ids = [row[0] for row in cursor.fetchall()] + + # 如果没有找到匹配的file_id记录,尝试通过文件名查找 + if not record_ids: + # 获取文件名(如果有的话) + file_name = request.json.get("file_name", "") + if file_name: + # 使用文件名和save_path进行精确匹配 + cursor.execute(""" + SELECT id FROM transfer_records + WHERE (original_name = ? OR renamed_to = ?) + AND save_path = ? + """, (file_name, file_name, save_path)) + + record_ids = [row[0] for row in cursor.fetchall()] + + # 删除找到的所有记录 + deleted_count = 0 + for record_id in record_ids: + deleted_count += db.delete_record(record_id) + + # 添加删除记录的信息到响应中 + response["deleted_records"] = deleted_count + # logging.info(f">>> 删除文件 {fid} 同时删除了 {deleted_count} 条相关记录") + + except Exception as e: + logging.error(f">>> 删除记录时出错: {str(e)}") + # 不影响主流程,即使删除记录失败也返回文件删除成功 else: response = {"success": False, "message": "缺失必要字段: fid"} return jsonify(response) diff --git a/app/sdk/db.py b/app/sdk/db.py index 302d8c5..316de4e 100644 --- a/app/sdk/db.py +++ b/app/sdk/db.py @@ -31,9 +31,17 @@ class RecordDB: resolution TEXT, modify_date INTEGER NOT NULL, file_id TEXT, - file_type TEXT + file_type TEXT, + save_path TEXT ) ''') + + # 检查save_path字段是否存在,如果不存在则添加 + cursor.execute("PRAGMA table_info(transfer_records)") + columns = [column[1] for column in cursor.fetchall()] + if 'save_path' not in columns: + cursor.execute('ALTER TABLE transfer_records ADD COLUMN save_path TEXT') + self.conn.commit() def close(self): @@ -41,19 +49,19 @@ class RecordDB: self.conn.close() def add_record(self, task_name, original_name, renamed_to, file_size, modify_date, - duration="", resolution="", file_id="", file_type=""): + duration="", resolution="", file_id="", file_type="", save_path=""): """添加一条转存记录""" cursor = self.conn.cursor() cursor.execute( "INSERT INTO transfer_records (transfer_time, task_name, original_name, renamed_to, file_size, " - "duration, resolution, modify_date, file_id, file_type) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", + "duration, resolution, modify_date, file_id, file_type, save_path) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", (int(time.time()), task_name, original_name, renamed_to, file_size, - duration, resolution, modify_date, file_id, file_type) + duration, resolution, modify_date, file_id, file_type, save_path) ) self.conn.commit() return cursor.lastrowid - def update_renamed_to(self, file_id, original_name, renamed_to, task_name=""): + def update_renamed_to(self, file_id, original_name, renamed_to, task_name="", save_path=""): """更新最近一条记录的renamed_to字段 Args: @@ -61,6 +69,7 @@ class RecordDB: original_name: 原文件名 renamed_to: 重命名后的文件名 task_name: 任务名称,可选项,如提供则作为附加筛选条件 + save_path: 保存路径,可选项,如提供则同时更新保存路径 Returns: 更新的记录数量 @@ -97,11 +106,17 @@ class RecordDB: if result: record_id = result[0] - # 更新记录 - cursor.execute( - "UPDATE transfer_records SET renamed_to = ? WHERE id = ?", - (renamed_to, record_id) - ) + # 根据是否提供save_path决定更新哪些字段 + if save_path: + cursor.execute( + "UPDATE transfer_records SET renamed_to = ?, save_path = ? WHERE id = ?", + (renamed_to, save_path, record_id) + ) + else: + cursor.execute( + "UPDATE transfer_records SET renamed_to = ? WHERE id = ?", + (renamed_to, record_id) + ) self.conn.commit() return cursor.rowcount @@ -124,7 +139,7 @@ class RecordDB: # 构建SQL查询 valid_columns = ["transfer_time", "task_name", "original_name", "renamed_to", - "file_size", "duration", "resolution", "modify_date"] + "file_size", "duration", "resolution", "modify_date", "save_path"] if sort_by not in valid_columns: sort_by = "transfer_time" @@ -140,7 +155,8 @@ class RecordDB: params.append(task_name_filter) if keyword_filter: - where_clauses.append("task_name LIKE ?") + where_clauses.append("(task_name LIKE ? OR original_name LIKE ?)") + params.append(f"%{keyword_filter}%") params.append(f"%{keyword_filter}%") where_clause = " AND ".join(where_clauses) @@ -192,4 +208,33 @@ class RecordDB: cursor = self.conn.cursor() cursor.execute("DELETE FROM transfer_records WHERE id = ?", (record_id,)) self.conn.commit() - return cursor.rowcount \ No newline at end of file + return cursor.rowcount + + def get_records_by_save_path(self, save_path, include_subpaths=False): + """根据保存路径查询记录 + + Args: + save_path: 要查询的保存路径 + include_subpaths: 是否包含子路径下的文件 + + Returns: + 匹配的记录列表 + """ + cursor = self.conn.cursor() + + if include_subpaths: + # 如果包含子路径,使用LIKE查询 + query = "SELECT * FROM transfer_records WHERE save_path LIKE ? ORDER BY transfer_time DESC" + cursor.execute(query, [f"{save_path}%"]) + else: + # 精确匹配路径 + query = "SELECT * FROM transfer_records WHERE save_path = ? ORDER BY transfer_time DESC" + cursor.execute(query, [save_path]) + + records = cursor.fetchall() + + # 将结果转换为字典列表 + if records: + columns = [col[0] for col in cursor.description] + return [dict(zip(columns, row)) for row in records] + return [] \ No newline at end of file diff --git a/app/static/css/main.css b/app/static/css/main.css index 89c394a..9ae9200 100644 --- a/app/static/css/main.css +++ b/app/static/css/main.css @@ -1633,10 +1633,11 @@ button.close:focus, /* 操作列 - 固定宽度 */ #fileSelectModal .table .col-action { - width: 53px; - min-width: 53px; - max-width: 53px; - text-align: center; + width: 188px; + min-width: 188px; + max-width: 188px; + text-align: left; + padding-left: 12px !important; } /* 确保单元格内容溢出时正确显示 */ @@ -3576,11 +3577,11 @@ input::-moz-list-button { /* 针对选择保存到的文件夹模式 - 带操作列的表格 */ #fileSelectModal[data-modal-type="target"] .breadcrumb { - min-width: 513px; /* 4列表格总宽度: 230px + 90px + 140px + 53px */ + min-width: 648px; /* 4列表格总宽度: 230px + 90px + 140px + 188px */ } #fileSelectModal[data-modal-type="target"] .table { - width: 513px; + width: 648px; } /* 针对命名预览模式 - 2列表格 */ diff --git a/app/templates/index.html b/app/templates/index.html index 7c54034..936284e 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -953,7 +953,8 @@ {{file.size | size}} {{file.updated_at | ts2date}} - 删除 + 删除文件 + 删除文件和记录 @@ -2290,12 +2291,23 @@ this.$delete(this.formData.magic_regex, key); } }, - deleteFile(fid, fname, isDir) { - if (!confirm(`确定要删除此项目吗?`)) { + deleteFile(fid, fname, isDir, deleteRecords = false) { + // 根据是否删除记录显示不同的确认提示 + let confirmMessage = deleteRecords + ? `确定要删除此项目及其关联记录吗?` + : `确定要删除此项目吗?`; + + if (!confirm(confirmMessage)) { return; } - axios.post('/delete_file', { fid: fid }) + // 获取当前路径作为save_path参数 + let save_path = ""; + if (this.fileSelect && this.fileSelect.paths) { + save_path = this.fileSelect.paths.map(item => item.name).join("/"); + } + + axios.post('/delete_file', { fid: fid, file_name: fname, delete_records: deleteRecords, save_path: save_path }) .then(response => { if (response.data.code === 0) { // 从列表中移除文件 @@ -2311,7 +2323,18 @@ } } - this.showToast('成功删除 1 个项目'); + // 显示成功消息,根据是否删除记录显示不同的消息 + if (deleteRecords) { + const deletedRecords = response.data.deleted_records || 0; + this.showToast(`成功删除 1 个项目${deletedRecords > 0 ? `及其关联的 ${deletedRecords} 条记录` : ''}`); + } else { + this.showToast('成功删除 1 个项目'); + } + + // 如果同时删除了记录,无论当前在哪个页面,都刷新历史记录 + if (deleteRecords) { + this.loadHistoryRecords(); + } } else { alert('删除失败: ' + response.data.message); } @@ -3403,36 +3426,53 @@ // 阻止事件冒泡 event.stopPropagation(); }, - deleteSelectedFiles(clickedFid, clickedFname, isDir) { + deleteSelectedFiles(clickedFid, clickedFname, isDir, deleteRecords = false) { // 如果是文件夹或者没有选中的文件,则按原来的方式删除单个文件 if (isDir || this.fileSelect.selectedFiles.length === 0) { - this.deleteFile(clickedFid, clickedFname, isDir); + this.deleteFile(clickedFid, clickedFname, isDir, deleteRecords); return; } // 如果点击的文件不在选中列表中,也按原来的方式删除单个文件 if (!this.fileSelect.selectedFiles.includes(clickedFid)) { - this.deleteFile(clickedFid, clickedFname, isDir); + this.deleteFile(clickedFid, clickedFname, isDir, deleteRecords); return; } // 多选删除 const selectedCount = this.fileSelect.selectedFiles.length; - // 根据选中数量使用不同的确认提示 - let confirmMessage = selectedCount === 1 - ? `确定要删除此项目吗?` - : `确定要删除选中的 ${selectedCount} 个项目吗?`; + // 根据选中数量和是否删除记录使用不同的确认提示 + let confirmMessage = ''; + if (deleteRecords) { + confirmMessage = selectedCount === 1 + ? `确定要删除此项目及其关联记录吗?` + : `确定要删除选中的 ${selectedCount} 个项目及其关联记录吗?`; + } else { + confirmMessage = selectedCount === 1 + ? `确定要删除此项目吗?` + : `确定要删除选中的 ${selectedCount} 个项目吗?`; + } if (confirm(confirmMessage)) { + // 获取当前路径作为save_path参数 + let save_path = ""; + if (this.fileSelect && this.fileSelect.paths) { + save_path = this.fileSelect.paths.map(item => item.name).join("/"); + } + // 创建一个Promise数组来处理所有删除请求 const deletePromises = this.fileSelect.selectedFiles.map(fid => { - return axios.post('/delete_file', { fid: fid }) + // 查找对应的文件对象,获取文件名 + const fileObj = this.fileSelect.fileList.find(file => file.fid === fid); + const fileName = fileObj ? fileObj.file_name : ''; + + return axios.post('/delete_file', { fid: fid, file_name: fileName, delete_records: deleteRecords, save_path: save_path }) .then(response => { - return { fid: fid, success: response.data.code === 0 }; + return { fid: fid, success: response.data.code === 0, deleted_records: response.data.deleted_records || 0 }; }) .catch(error => { - return { fid: fid, success: false }; + return { fid: fid, success: false, deleted_records: 0 }; }); }); @@ -3442,6 +3482,8 @@ // 统计成功和失败的数量 const successCount = results.filter(r => r.success).length; const failCount = results.length - successCount; + // 统计删除的记录数 + const totalDeletedRecords = results.reduce((sum, r) => sum + (r.deleted_records || 0), 0); // 从文件列表中移除成功删除的文件 const successfullyDeletedFids = results.filter(r => r.success).map(r => r.fid); @@ -3455,7 +3497,17 @@ if (failCount > 0) { alert(`成功删除 ${successCount} 个项目,${failCount} 个项目删除失败`); } else { - this.showToast(`成功删除 ${successCount} 个项目`); + // 根据是否删除记录显示不同的消息 + if (deleteRecords) { + this.showToast(`成功删除 ${successCount} 个项目${totalDeletedRecords > 0 ? `及其关联的 ${totalDeletedRecords} 条记录` : ''}`); + } else { + this.showToast(`成功删除 ${successCount} 个项目`); + } + } + + // 如果同时删除了记录,无论当前在哪个页面都刷新历史记录 + if (deleteRecords) { + this.loadHistoryRecords(); } }); } diff --git a/quark_auto_save.py b/quark_auto_save.py index 6c9e28a..0bb496c 100644 --- a/quark_auto_save.py +++ b/quark_auto_save.py @@ -1146,6 +1146,19 @@ class Quark: # 目前只是添加占位符,未来可以扩展功能 pass + # 获取保存路径 + save_path = task.get("savepath", "") + # 如果file_info中有子目录路径信息,则拼接完整路径 + subdir_path = file_info.get("subdir_path", "") + if subdir_path: + # 确保路径格式正确,避免双斜杠 + if save_path.endswith('/') and subdir_path.startswith('/'): + save_path = save_path + subdir_path[1:] + elif not save_path.endswith('/') and not subdir_path.startswith('/'): + save_path = save_path + '/' + subdir_path + else: + save_path = save_path + subdir_path + # 添加记录到数据库 db.add_record( task_name=task.get("taskname", ""), @@ -1156,7 +1169,8 @@ class Quark: duration=duration, resolution=resolution, file_id=file_id, - file_type=file_type + file_type=file_type, + save_path=save_path ) # 关闭数据库连接 @@ -1195,12 +1209,26 @@ class Quark: file_id = file_info.get("fid", "") task_name = task.get("taskname", "") + # 获取保存路径 + save_path = task.get("savepath", "") + # 如果file_info中有子目录路径信息,则拼接完整路径 + subdir_path = file_info.get("subdir_path", "") + if subdir_path: + # 确保路径格式正确,避免双斜杠 + if save_path.endswith('/') and subdir_path.startswith('/'): + save_path = save_path + subdir_path[1:] + elif not save_path.endswith('/') and not subdir_path.startswith('/'): + save_path = save_path + '/' + subdir_path + else: + save_path = save_path + subdir_path + # 更新记录 updated = db.update_renamed_to( file_id=file_id, original_name=original_name, renamed_to=renamed_to, - task_name=task_name + task_name=task_name, + save_path=save_path ) # 关闭数据库连接 @@ -1255,12 +1283,17 @@ class Quark: # 使用原文件名和任务名查找记录 task_name = task.get("taskname", "") + # 获取保存路径 + save_path = task.get("savepath", "") + # 注意:从日志中无法获取子目录信息,只能使用任务的主保存路径 + # 更新记录 updated = db.update_renamed_to( file_id="", # 不使用file_id查询,因为在日志中无法获取 original_name=old_name, renamed_to=new_name, - task_name=task_name + task_name=task_name, + save_path=save_path ) # 关闭数据库连接 From bc8802b11e31b9b0d5a0af0819cbd42deb7cfd30 Mon Sep 17 00:00:00 2001 From: x1ao4 Date: Sun, 25 May 2025 23:34:37 +0800 Subject: [PATCH 06/11] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E9=87=8D=E7=BD=AE?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E5=A4=B9=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/run.py | 78 ++++++++++++++++++++++++++++++++++++++++ app/static/css/main.css | 5 +++ app/templates/index.html | 48 +++++++++++++++++++++++++ 3 files changed, 131 insertions(+) diff --git a/app/run.py b/app/run.py index 2f637b2..5225bd8 100644 --- a/app/run.py +++ b/app/run.py @@ -1010,6 +1010,84 @@ def get_user_info(): return jsonify({"success": True, "data": user_info_list}) +# 重置文件夹(删除文件夹内所有文件和相关记录) +@app.route("/reset_folder", methods=["POST"]) +def reset_folder(): + if not is_login(): + return jsonify({"success": False, "message": "未登录"}) + + # 获取请求参数 + save_path = request.json.get("save_path", "") + task_name = request.json.get("task_name", "") + + if not save_path: + return jsonify({"success": False, "message": "保存路径不能为空"}) + + try: + # 初始化夸克网盘客户端 + account = Quark(config_data["cookie"][0], 0) + + # 1. 获取文件夹ID + # 先检查是否已有缓存的文件夹ID + folder_fid = account.savepath_fid.get(save_path) + + # 如果没有缓存的ID,则尝试创建文件夹以获取ID + if not folder_fid: + mkdir_result = account.mkdir(save_path) + if mkdir_result.get("code") == 0: + folder_fid = mkdir_result["data"]["fid"] + account.savepath_fid[save_path] = folder_fid + else: + return jsonify({"success": False, "message": f"获取文件夹ID失败: {mkdir_result.get('message', '未知错误')}"}) + + # 2. 获取文件夹内的所有文件 + file_list = account.ls_dir(folder_fid) + if isinstance(file_list, dict) and file_list.get("error"): + return jsonify({"success": False, "message": f"获取文件列表失败: {file_list.get('error', '未知错误')}"}) + + # 收集所有文件ID + file_ids = [] + for item in file_list: + file_ids.append(item["fid"]) + + # 3. 删除所有文件 + deleted_files = 0 + if file_ids: + delete_result = account.delete(file_ids) + if delete_result.get("code") == 0: + deleted_files = len(file_ids) + + # 4. 删除相关的历史记录 + deleted_records = 0 + try: + # 初始化数据库 + db = RecordDB() + + # 查询与该保存路径相关的所有记录 + cursor = db.conn.cursor() + cursor.execute("SELECT id FROM transfer_records WHERE save_path = ?", (save_path,)) + record_ids = [row[0] for row in cursor.fetchall()] + + # 删除找到的所有记录 + for record_id in record_ids: + deleted_records += db.delete_record(record_id) + + except Exception as e: + logging.error(f">>> 删除记录时出错: {str(e)}") + # 即使删除记录失败,也返回文件删除成功 + + return jsonify({ + "success": True, + "message": f"重置成功,删除了 {deleted_files} 个文件和 {deleted_records} 条记录", + "deleted_files": deleted_files, + "deleted_records": deleted_records + }) + + except Exception as e: + logging.error(f">>> 重置文件夹时出错: {str(e)}") + return jsonify({"success": False, "message": f"重置文件夹时出错: {str(e)}"}) + + if __name__ == "__main__": init() reload_tasks() diff --git a/app/static/css/main.css b/app/static/css/main.css index 9ae9200..1d97288 100644 --- a/app/static/css/main.css +++ b/app/static/css/main.css @@ -805,6 +805,11 @@ main div[v-if="activeTab === 'config'"] .row.title:first-child { font-size: 0.98rem; } +/* 重置文件夹图标样式 */ +.bi-folder-x { + font-size: 0.98rem; +} + /* 恢复图标样式 */ .bi-reply { color: var(--dark-text-color); diff --git a/app/templates/index.html b/app/templates/index.html index 936284e..eb0e9bd 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -554,6 +554,7 @@
+
@@ -3536,6 +3537,53 @@ event.preventDefault(); } }, + resetFolder(index) { + // 重置文件夹 + this.formData.tasklist[index].savepath = this.formData.tasklist[index].savepath.split('/').slice(0, -1).join('/'); + if (this.formData.tasklist[index].savepath.endsWith('/')) { + this.formData.tasklist[index].savepath = this.formData.tasklist[index].savepath.slice(0, -1); + } + this.showToast('文件夹已重置'); + }, + resetFolder(index) { + // 获取当前任务的保存路径 + const savePath = this.formData.tasklist[index].savepath; + const taskName = this.formData.tasklist[index].taskname; + + if (!savePath) { + this.showToast('保存路径为空,无法重置'); + return; + } + + // 显示确认对话框 + if (confirm(`确定要重置文件夹「${savePath}」吗?`)) { + // 显示加载状态 + this.modalLoading = true; + + // 调用后端API + axios.post('/reset_folder', { + save_path: savePath, + task_name: taskName + }) + .then(response => { + if (response.data.success) { + this.showToast(`重置成功:删除了 ${response.data.deleted_files || 0} 个文件,${response.data.deleted_records || 0} 条记录`); + // 如果当前是历史记录页面,刷新记录 + if (this.activeTab === 'history') { + this.loadHistoryRecords(); + } + } else { + alert(response.data.message || '重置文件夹失败'); + } + this.modalLoading = false; + }) + .catch(error => { + console.error('重置文件夹出错:', error); + alert('重置文件夹出错: ' + (error.response?.data?.message || error.message)); + this.modalLoading = false; + }); + } + }, } }); From bced07dbe344e1fb9f9ee35d9d6cbb44401c3ef7 Mon Sep 17 00:00:00 2001 From: x1ao4 Date: Mon, 26 May 2025 02:21:31 +0800 Subject: [PATCH 07/11] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E9=80=9A=E8=BF=87?= =?UTF-8?q?=E5=8E=86=E5=8F=B2=E8=BD=AC=E5=AD=98=E8=AE=B0=E5=BD=95=E4=BC=98?= =?UTF-8?q?=E5=85=88=E6=9F=A5=E9=87=8D=E7=9A=84=E6=9F=A5=E9=87=8D=E9=80=BB?= =?UTF-8?q?=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/static/css/main.css | 4 +- quark_auto_save.py | 100 ++++++++++++++++++++++++++++++++++++---- 2 files changed, 93 insertions(+), 11 deletions(-) diff --git a/app/static/css/main.css b/app/static/css/main.css index 1d97288..1c1d86c 100644 --- a/app/static/css/main.css +++ b/app/static/css/main.css @@ -1798,7 +1798,7 @@ button.close:focus, background-color: transparent !important; color: inherit !important; font-size: inherit !important; - font-weight: bold !important; + font-weight: normal !important; padding: 0 !important; margin: 0 !important; display: inline-flex !important; @@ -3311,7 +3311,7 @@ div[id^="collapse_"][id*="plugin"] .input-group { background-color: transparent; color: inherit; font-size: inherit; - font-weight: inherit; + font-weight: normal; padding: 0; margin: 0; } diff --git a/quark_auto_save.py b/quark_auto_save.py index 0bb496c..2ef97d7 100644 --- a/quark_auto_save.py +++ b/quark_auto_save.py @@ -1287,14 +1287,35 @@ class Quark: save_path = task.get("savepath", "") # 注意:从日志中无法获取子目录信息,只能使用任务的主保存路径 + # 检查文件是否已存在于记录中 + # 先查询是否有匹配的记录 + cursor = db.conn.cursor() + query = "SELECT file_id FROM transfer_records WHERE original_name = ? AND task_name = ? AND save_path = ?" + cursor.execute(query, (old_name, task_name, save_path)) + result = cursor.fetchone() + + # 如果找到了匹配的记录,使用file_id进行更新 + file_id = result[0] if result else "" + # 更新记录 - updated = db.update_renamed_to( - file_id="", # 不使用file_id查询,因为在日志中无法获取 - original_name=old_name, - renamed_to=new_name, - task_name=task_name, - save_path=save_path - ) + if file_id: + # 使用file_id更新 + updated = db.update_renamed_to( + file_id=file_id, + original_name="", # 不使用原文件名,因为已有file_id + renamed_to=new_name, + task_name=task_name, + save_path=save_path + ) + else: + # 使用原文件名更新 + updated = db.update_renamed_to( + file_id="", # 不使用file_id查询,因为在日志中无法获取 + original_name=old_name, + renamed_to=new_name, + task_name=task_name, + save_path=save_path + ) # 关闭数据库连接 db.close() @@ -1303,7 +1324,7 @@ class Quark: except Exception as e: print(f"根据日志更新转存记录失败: {e}") return False - + # 批量处理重命名日志 def process_rename_logs(self, task, rename_logs): """处理重命名日志列表,更新数据库记录 @@ -1315,6 +1336,49 @@ class Quark: for log in rename_logs: if "重命名:" in log and "→" in log and "失败" not in log: self.update_transfer_record_from_log(task, log) + + def check_file_exists_in_records(self, file_id, task=None): + """检查文件ID是否存在于转存记录中 + + Args: + file_id: 要检查的文件ID + task: 可选的任务信息,用于进一步筛选 + + Returns: + bool: 文件是否已存在于记录中 + """ + if not file_id: + return False + + try: + # 初始化数据库 + db = RecordDB() + + # 构建查询条件 + conditions = ["file_id = ?"] + params = [file_id] + + # 如果提供了任务信息,添加任务名称条件 + if task and task.get("taskname"): + conditions.append("task_name = ?") + params.append(task.get("taskname")) + + # 构建WHERE子句 + where_clause = " AND ".join(conditions) + + # 查询是否存在匹配的记录 + cursor = db.conn.cursor() + query = f"SELECT COUNT(*) FROM transfer_records WHERE {where_clause}" + cursor.execute(query, params) + count = cursor.fetchone()[0] + + # 关闭数据库连接 + db.close() + + return count > 0 + except Exception as e: + print(f"检查文件记录时出错: {e}") + return False def do_save_task(self, task): # 判断资源失效记录 @@ -1596,8 +1660,14 @@ class Quark: filtered_share_files = [] for share_file in share_file_list: if share_file["dir"]: - # 不再直接添加目录到filtered_share_files # 目录处理会在后续专门的循环中进行 + filtered_share_files.append(share_file) + continue + + # 检查文件ID是否存在于转存记录中 + file_id = share_file.get("fid", "") + if file_id and self.check_file_exists_in_records(file_id, task): + # 文件ID已存在于记录中,跳过处理 continue file_size = share_file.get("size", 0) @@ -1808,6 +1878,12 @@ class Quark: # 添加符合的 for share_file in share_file_list: + # 检查文件ID是否存在于转存记录中 + file_id = share_file.get("fid", "") + if file_id and self.check_file_exists_in_records(file_id, task): + # 文件ID已存在于记录中,跳过处理 + continue + # 检查文件是否已存在(通过大小和扩展名)- 新增的文件查重逻辑 is_duplicate = False if not share_file["dir"]: # 文件夹不进行内容查重 @@ -2505,6 +2581,12 @@ class Quark: if task.get("update_subdir") and re.search(task["update_subdir"], share_file["file_name"]): filtered_share_files.append(share_file) continue + + # 检查文件ID是否存在于转存记录中 + file_id = share_file.get("fid", "") + if file_id and self.check_file_exists_in_records(file_id, task): + # 文件ID已存在于记录中,跳过处理 + continue # 从共享文件中提取剧集号 episode_num = extract_episode_number_local(share_file["file_name"]) From f48c06efc8e45a507952d5b5530b34e814c9c94f Mon Sep 17 00:00:00 2001 From: x1ao4 Date: Mon, 26 May 2025 15:42:53 +0800 Subject: [PATCH 08/11] =?UTF-8?q?=E4=B8=BA=20Aria2=20=E6=8F=92=E4=BB=B6?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E4=BA=86=E6=88=90=E5=8A=9F=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E4=B8=8B=E8=BD=BD=E4=BB=BB=E5=8A=A1=E5=90=8E=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E5=88=A0=E9=99=A4=E5=A4=B8=E5=85=8B=E7=BD=91=E7=9B=98=E5=AF=B9?= =?UTF-8?q?=E5=BA=94=E6=96=87=E4=BB=B6=E7=9A=84=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugins/aria2.py | 40 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/plugins/aria2.py b/plugins/aria2.py index 9c78a26..f462469 100644 --- a/plugins/aria2.py +++ b/plugins/aria2.py @@ -29,6 +29,7 @@ class Aria2: default_task_config = { "auto_download": False, # 是否自动添加下载任务 "pause": False, # 添加任务后为暂停状态,不自动开始(手动下载) + "auto_delete_quark_files": False, # 是否在添加下载任务后自动删除夸克网盘文件 } is_active = False rpc_url = None @@ -116,6 +117,7 @@ class Aria2: # 筛选出当次转存的文件 file_fids = [] file_paths = [] + file_info = [] # 存储文件的完整信息,包括ID和路径 for file in dir_files: if file.get("dir", False): @@ -132,7 +134,14 @@ class Aria2: if is_current_file: file_fids.append(file["fid"]) - file_paths.append(f"{savepath}/{file_name}") + file_path = f"{savepath}/{file_name}" + file_paths.append(file_path) + # 保存完整信息 + file_info.append({ + "fid": file["fid"], + "path": file_path, + "name": file_name + }) if not file_fids: print("📝 Aria2: 未能匹配到需要下载的文件") @@ -161,6 +170,9 @@ class Aria2: # 使用全局排序函数对文件进行排序 download_items.sort(key=lambda x: x["sort_key"]) + + # 记录成功添加到下载队列的文件信息,用于后续删除 + downloaded_files = [] # 按排序后的顺序下载文件 for item in download_items: @@ -193,8 +205,34 @@ class Aria2: ] try: self.add_uri(aria2_params) + # 记录成功添加到下载队列的文件信息 + idx = file_paths.index(file_path) + if idx >= 0 and idx < len(file_info): + downloaded_files.append(file_info[idx]) except Exception as e: print(f"📥 Aria2 添加下载任务失败: {e}") + + # 如果配置了自动删除且有成功添加下载任务的文件,则删除夸克网盘中的文件 + if task_config.get("auto_delete_quark_files") and downloaded_files: + try: + # 提取要删除的文件ID + files_to_delete = [] + for file_data in downloaded_files: + # 再次确认文件路径,确保只删除指定目录下的文件 + if file_data["path"].startswith(savepath): + files_to_delete.append(file_data["fid"]) + + if files_to_delete: + account.delete(files_to_delete) + except Exception as e: + print(f"📝 Aria2: 删除夸克网盘文件失败: {e}") + else: + if not task_config.get("auto_delete_quark_files"): + # 未启用自动删除,不需要输出信息 + pass + elif not downloaded_files: + # 没有需要删除的文件,不需要输出信息 + pass def _make_rpc_request(self, method, params=None): """发出 JSON-RPC 请求.""" From 762f345d79afee3855bc1007eccdfd885ff10888 Mon Sep 17 00:00:00 2001 From: x1ao4 Date: Mon, 26 May 2025 17:12:42 +0800 Subject: [PATCH 09/11] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=8F=92=E4=BB=B6?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E5=90=8C=E6=AD=A5=E6=9B=B4=E6=96=B0=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/run.py | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/app/run.py b/app/run.py index 5225bd8..851f311 100644 --- a/app/run.py +++ b/app/run.py @@ -187,6 +187,62 @@ def get_data(): return jsonify({"success": True, "data": data}) +def sync_task_plugins_config(): + """同步更新所有任务的插件配置 + + 1. 检查每个任务的插件配置 + 2. 如果插件配置不存在,使用默认配置 + 3. 如果插件配置存在但缺少新的配置项,添加默认值 + 4. 保留原有的自定义配置 + 5. 只处理已启用的插件(通过PLUGIN_FLAGS检查) + 6. 清理被禁用插件的配置 + """ + global config_data, task_plugins_config_default + + # 如果没有任务列表,直接返回 + if not config_data.get("tasklist"): + return + + # 获取禁用的插件列表 + disabled_plugins = set() + if PLUGIN_FLAGS: + disabled_plugins = {name.lstrip('-') for name in PLUGIN_FLAGS.split(',')} + + # 遍历所有任务 + for task in config_data["tasklist"]: + # 确保任务有addition字段 + if "addition" not in task: + task["addition"] = {} + + # 清理被禁用插件的配置 + for plugin_name in list(task["addition"].keys()): + if plugin_name in disabled_plugins: + del task["addition"][plugin_name] + + # 遍历所有插件的默认配置 + for plugin_name, default_config in task_plugins_config_default.items(): + # 跳过被禁用的插件 + if plugin_name in disabled_plugins: + continue + + # 如果任务中没有该插件的配置,添加默认配置 + if plugin_name not in task["addition"]: + task["addition"][plugin_name] = default_config.copy() + else: + # 如果任务中有该插件的配置,检查是否有新的配置项 + current_config = task["addition"][plugin_name] + # 确保current_config是字典类型 + if not isinstance(current_config, dict): + # 如果不是字典类型,使用默认配置 + task["addition"][plugin_name] = default_config.copy() + continue + + # 遍历默认配置的每个键值对 + for key, default_value in default_config.items(): + if key not in current_config: + current_config[key] = default_value + + # 更新数据 @app.route("/update", methods=["POST"]) def update(): @@ -202,6 +258,10 @@ def update(): config_data["webui"]["password"] = value.get("password", config_data["webui"]["password"]) else: config_data.update({key: value}) + + # 同步更新任务的插件配置 + sync_task_plugins_config() + Config.write_json(CONFIG_PATH, config_data) # 更新session token,确保当前会话在用户名密码更改后仍然有效 session["token"] = get_login_token() @@ -815,6 +875,22 @@ def init(): _, plugins_config_default, task_plugins_config_default = Config.load_plugins() plugins_config_default.update(config_data.get("plugins", {})) config_data["plugins"] = plugins_config_default + + # 获取禁用的插件列表 + disabled_plugins = set() + if PLUGIN_FLAGS: + disabled_plugins = {name.lstrip('-') for name in PLUGIN_FLAGS.split(',')} + + # 清理所有任务中被禁用插件的配置 + if config_data.get("tasklist"): + for task in config_data["tasklist"]: + if "addition" in task: + for plugin_name in list(task["addition"].keys()): + if plugin_name in disabled_plugins: + del task["addition"][plugin_name] + + # 同步更新任务的插件配置 + sync_task_plugins_config() # 更新配置 Config.write_json(CONFIG_PATH, config_data) From 1f3e5ebc83bcbd286a596ee3e69db7916986d84c Mon Sep 17 00:00:00 2001 From: x1ao4 <112841659+x1ao4@users.noreply.github.com> Date: Mon, 26 May 2025 17:38:21 +0800 Subject: [PATCH 10/11] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=E8=AF=B4=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a2ca6ac..6a21e7d 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,8 @@ - **数据库**:引入 SQLite 数据库,记录和管理所有转存历史,便于查询和追踪。 - **转存记录**:支持通过 WebUI 的转存记录页面查看、查询历史转存记录的相关信息,支持删除转存记录。 - **WebUI**:对整个 WebUI 进行了重塑,增加了更多实用功能,如文件选择和预览界面的排序功能、资源搜索的过滤功能、TMDB 和豆瓣搜索功能、页面视图切换功能、账号设置功能等等。 +- **查重逻辑**:支持优先通过历史转存记录查重,对于有转存记录的文件,即使删除网盘内的文件,也不会重复转存。 +- **Aria2**:支持成功添加 Aria2 下载任务后自动删除夸克网盘内对应的文件,清理网盘空间。 本项目修改后的版本为个人需求定制版,目的是满足我自己的使用需求,某些(我不用的)功能可能会因为修改而出现 BUG,不一定会被修复。若你要使用本项目,请知晓本人不是程序员,我无法保证本项目的稳定性,如果你在使用过程中发现了 BUG,可以在 Issues 中提交,但不保证每个 BUG 都能被修复,请谨慎使用,风险自担。 @@ -32,7 +34,7 @@ - 文件管理 - [x] 目标目录不存在时自动新建 - - [x] 跳过已转存过的文件 + - [x] 跳过已转存过的文件(**即使删除网盘文件,也不会重复转存**) - [x] **过滤不需要转存的文件或文件夹** - [x] 转存后文件名整理(正则命名、**顺序命名**、**剧集命名**) - [x] 可选忽略文件后缀 @@ -53,6 +55,7 @@ - [x] 每日签到领空间 [?](https://github.com/x1ao4/quark-auto-save-x/wiki/使用技巧集锦#每日签到领空间) - [x] 支持多个通知推送渠道 [?](https://github.com/x1ao4/quark-auto-save-x/wiki/通知推送服务配置) - [x] 支持多账号(多账号签到,仅首账号转存) + - [x] 支持网盘文件下载、strm 文件生成等功能 [?](https://github.com/x1ao4/quark-auto-save-x/wiki/插件配置) ## 部署 ### Docker 部署 From 8aa96e87581b1301c10e37e8f1cb7efef641a30d Mon Sep 17 00:00:00 2001 From: x1ao4 <112841659+x1ao4@users.noreply.github.com> Date: Mon, 26 May 2025 17:40:53 +0800 Subject: [PATCH 11/11] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=E8=AF=B4=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6a21e7d..29d76d5 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ - **数据库**:引入 SQLite 数据库,记录和管理所有转存历史,便于查询和追踪。 - **转存记录**:支持通过 WebUI 的转存记录页面查看、查询历史转存记录的相关信息,支持删除转存记录。 - **WebUI**:对整个 WebUI 进行了重塑,增加了更多实用功能,如文件选择和预览界面的排序功能、资源搜索的过滤功能、TMDB 和豆瓣搜索功能、页面视图切换功能、账号设置功能等等。 -- **查重逻辑**:支持优先通过历史转存记录查重,对于有转存记录的文件,即使删除网盘内的文件,也不会重复转存。 +- **查重逻辑**:支持优先通过历史转存记录查重,对于有转存记录的文件,即使删除网盘文件,也不会重复转存。 - **Aria2**:支持成功添加 Aria2 下载任务后自动删除夸克网盘内对应的文件,清理网盘空间。 本项目修改后的版本为个人需求定制版,目的是满足我自己的使用需求,某些(我不用的)功能可能会因为修改而出现 BUG,不一定会被修复。若你要使用本项目,请知晓本人不是程序员,我无法保证本项目的稳定性,如果你在使用过程中发现了 BUG,可以在 Issues 中提交,但不保证每个 BUG 都能被修复,请谨慎使用,风险自担。