diff --git a/app/run.py b/app/run.py index 2921619..ef08451 100644 --- a/app/run.py +++ b/app/run.py @@ -28,6 +28,8 @@ import re import random import time import treelib +from functools import lru_cache +from threading import Lock parent_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) sys.path.insert(0, parent_dir) @@ -96,6 +98,45 @@ PORT = int(os.environ.get("PORT", "5005")) config_data = {} task_plugins_config_default = {} +# 文件列表缓存 +file_list_cache = {} +cache_lock = Lock() + +# 默认性能参数(如果配置中没有设置) +DEFAULT_PERFORMANCE_CONFIG = { + "api_page_size": 200, + "cache_expire_time": 30 +} + +def get_performance_config(): + """获取性能配置参数""" + try: + if config_data and "file_performance" in config_data: + perf_config = config_data["file_performance"] + # 确保所有值都是整数类型 + result = {} + for key, default_value in DEFAULT_PERFORMANCE_CONFIG.items(): + try: + result[key] = int(perf_config.get(key, default_value)) + except (ValueError, TypeError): + result[key] = default_value + return result + except Exception as e: + print(f"获取性能配置失败: {e}") + return DEFAULT_PERFORMANCE_CONFIG + +def cleanup_expired_cache(): + """清理所有过期缓存""" + current_time = time.time() + perf_config = get_performance_config() + cache_expire_time = perf_config.get("cache_expire_time", 30) + + with cache_lock: + expired_keys = [k for k, (_, t) in file_list_cache.items() if current_time - t > cache_expire_time] + for k in expired_keys: + del file_list_cache[k] + return len(expired_keys) + app = Flask(__name__) app.config["APP_VERSION"] = get_app_ver() app.secret_key = "ca943f6db6dd34823d36ab08d8d6f65d" @@ -683,7 +724,15 @@ def get_share_detail(): def get_savepath_detail(): if not is_login(): return jsonify({"success": False, "message": "未登录"}) - account = Quark(config_data["cookie"][0], 0) + + # 获取账号索引参数 + account_index = int(request.args.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) paths = [] if path := request.args.get("path"): if path == "/": @@ -718,7 +767,15 @@ def get_savepath_detail(): def delete_file(): if not is_login(): return jsonify({"success": False, "message": "未登录"}) - account = Quark(config_data["cookie"][0], 0) + + # 获取账号索引参数 + 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) if fid := request.json.get("fid"): response = account.delete([fid]) @@ -805,6 +862,15 @@ def add_task(): # 定时任务执行的函数 def run_python(args): logging.info(f">>> 定时运行任务") + + # 在定时任务开始前清理过期缓存 + try: + cleaned_count = cleanup_expired_cache() + if cleaned_count > 0: + logging.info(f">>> 清理了 {cleaned_count} 个过期缓存项") + except Exception as e: + logging.warning(f">>> 清理缓存时出错: {e}") + # 检查是否需要随机延迟执行 if delay := config_data.get("crontab_delay"): try: @@ -816,7 +882,7 @@ def run_python(args): time.sleep(random_delay) except (ValueError, TypeError): logging.warning(f">>> 延迟执行设置无效: {delay}") - + os.system(f"{PYTHON_PATH} {args}") @@ -1075,7 +1141,7 @@ def format_records(records): def get_user_info(): if not is_login(): return jsonify({"success": False, "message": "未登录"}) - + user_info_list = [] for idx, cookie in enumerate(config_data["cookie"]): account = Quark(cookie, idx) @@ -1095,26 +1161,101 @@ def get_user_info(): "is_active": False, "has_mparam": has_mparam }) - + return jsonify({"success": True, "data": user_info_list}) +@app.route("/get_accounts_detail") +def get_accounts_detail(): + """获取所有账号的详细信息,包括昵称和空间使用情况""" + if not is_login(): + return jsonify({"success": False, "message": "未登录"}) + + accounts_detail = [] + for idx, cookie in enumerate(config_data["cookie"]): + account = Quark(cookie, idx) + account_info = account.init() + + # 如果无法获取账号信息,检查是否有移动端参数 + if not account_info: + has_mparam = bool(account.mparam) + # 如果只有移动端参数,跳过此账号(不显示在文件整理页面的账号选择栏中) + if has_mparam: + continue + else: + # 如果既没有账号信息也没有移动端参数,显示为未登录 + account_detail = { + "index": idx, + "nickname": "", + "is_active": False, + "used_space": 0, + "total_space": 0, + "usage_rate": 0, + "display_text": f"账号{idx + 1}(未登录)" + } + accounts_detail.append(account_detail) + continue + + # 成功获取账号信息的情况 + account_detail = { + "index": idx, + "nickname": account_info["nickname"], + "is_active": account.is_active, + "used_space": 0, + "total_space": 0, + "usage_rate": 0, + "display_text": "" + } + + # 检查是否有移动端参数 + has_mparam = bool(account.mparam) + + if has_mparam: + # 同时有cookie和移动端参数,尝试获取空间信息 + try: + growth_info = account.get_growth_info() + if growth_info: + total_capacity = growth_info.get("total_capacity", 0) + account_detail["total_space"] = total_capacity + # 显示昵称和总容量 + total_str = format_bytes(total_capacity) + account_detail["display_text"] = f"{account_info['nickname']} · {total_str}" + else: + # 获取空间信息失败,只显示昵称 + account_detail["display_text"] = account_info["nickname"] + except Exception as e: + logging.error(f"获取账号 {idx} 空间信息失败: {str(e)}") + # 获取空间信息失败,只显示昵称 + account_detail["display_text"] = account_info["nickname"] + else: + # 只有cookie,没有移动端参数,只显示昵称 + account_detail["display_text"] = account_info["nickname"] + + accounts_detail.append(account_detail) + + return jsonify({"success": True, "data": accounts_detail}) + + # 重置文件夹(删除文件夹内所有文件和相关记录) @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", "") - + account_index = int(request.json.get("account_index", 0)) # 新增账号索引参数 + if not save_path: return jsonify({"success": False, "message": "保存路径不能为空"}) - + try: + # 验证账号索引 + if account_index < 0 or account_index >= len(config_data["cookie"]): + return jsonify({"success": False, "message": "账号索引无效"}) + # 初始化夸克网盘客户端 - account = Quark(config_data["cookie"][0], 0) + account = Quark(config_data["cookie"][account_index], account_index) # 1. 获取文件夹ID # 先检查是否已有缓存的文件夹ID @@ -1182,18 +1323,24 @@ def reset_folder(): def get_file_list(): if not is_login(): return jsonify({"success": False, "message": "未登录"}) - + # 获取请求参数 folder_id = request.args.get("folder_id", "root") sort_by = request.args.get("sort_by", "file_name") order = request.args.get("order", "asc") page = int(request.args.get("page", 1)) page_size = int(request.args.get("page_size", 15)) - + account_index = int(request.args.get("account_index", 0)) + force_refresh = request.args.get("force_refresh", "false").lower() == "true" # 新增账号索引参数 + try: + # 验证账号索引 + if account_index < 0 or account_index >= len(config_data["cookie"]): + return jsonify({"success": False, "message": "账号索引无效"}) + # 初始化夸克网盘客户端 - account = Quark(config_data["cookie"][0], 0) - + account = Quark(config_data["cookie"][account_index], account_index) + # 获取文件列表 if folder_id == "root": folder_id = "0" # 根目录的ID为0 @@ -1201,43 +1348,92 @@ def get_file_list(): else: # 获取当前文件夹的路径 paths = account.get_paths(folder_id) - - # 获取文件列表 - files = account.ls_dir(folder_id) + + # 获取性能配置 + perf_config = get_performance_config() + api_page_size = perf_config.get("api_page_size", 200) + cache_expire_time = perf_config.get("cache_expire_time", 30) + + # 缓存键 + cache_key = f"{account_index}_{folder_id}_{sort_by}_{order}" + current_time = time.time() + + # 检查缓存(除非强制刷新) + if not force_refresh: + with cache_lock: + if cache_key in file_list_cache: + cache_data, cache_time = file_list_cache[cache_key] + if current_time - cache_time < cache_expire_time: + # 使用缓存数据 + cached_files = cache_data + total = len(cached_files) + start_idx = (page - 1) * page_size + end_idx = min(start_idx + page_size, total) + paginated_files = cached_files[start_idx:end_idx] + + return jsonify({ + "success": True, + "data": { + "list": paginated_files, + "total": total, + "paths": paths + } + }) + else: + # 强制刷新时,清除当前目录的缓存 + with cache_lock: + if cache_key in file_list_cache: + del file_list_cache[cache_key] + + # 无论分页还是全部模式,都必须获取所有文件才能进行正确的全局排序 + files = account.ls_dir(folder_id, page_size=api_page_size) + if isinstance(files, dict) and files.get("error"): - return jsonify({"success": False, "message": f"获取文件列表失败: {files.get('error', '未知错误')}"}) - + # 检查是否是目录不存在的错误 + error_msg = files.get('error', '未知错误') + if "不存在" in error_msg or "无效" in error_msg or "找不到" in error_msg: + return jsonify({"success": False, "message": f"目录不存在或无权限访问: {error_msg}"}) + else: + return jsonify({"success": False, "message": f"获取文件列表失败: {error_msg}"}) + # 计算总数 total = len(files) - - # 排序 - if sort_by == "file_name": - files.sort(key=lambda x: x["file_name"].lower()) - elif sort_by == "file_size": - files.sort(key=lambda x: x["size"] if not x["dir"] else 0) - else: # updated_at - files.sort(key=lambda x: x["updated_at"]) - if order == "desc": - files.reverse() + # 优化排序:使用更高效的排序方法 + def get_sort_key(file_item): + if sort_by == "file_name": + return file_item["file_name"].lower() + elif sort_by == "file_size": + return file_item["size"] if not file_item["dir"] else 0 + else: # updated_at + return file_item["updated_at"] - # 根据排序字段决定是否将目录放在前面 + # 分离文件夹和文件以优化排序 if sort_by == "updated_at": # 修改日期排序时严格按照日期排序,不区分文件夹和文件 + files.sort(key=get_sort_key, reverse=(order == "desc")) sorted_files = files else: - # 其他排序时目录始终在前面 + # 其他排序时目录始终在前面,分别排序以提高效率 directories = [f for f in files if f["dir"]] normal_files = [f for f in files if not f["dir"]] + + directories.sort(key=get_sort_key, reverse=(order == "desc")) + normal_files.sort(key=get_sort_key, reverse=(order == "desc")) + sorted_files = directories + normal_files - + + # 更新缓存 + with cache_lock: + file_list_cache[cache_key] = (sorted_files, current_time) + # 分页 start_idx = (page - 1) * page_size end_idx = min(start_idx + page_size, total) paginated_files = sorted_files[start_idx:end_idx] - + return jsonify({ - "success": True, + "success": True, "data": { "list": paginated_files, "total": total, @@ -1263,13 +1459,18 @@ def preview_rename(): naming_mode = request.args.get("naming_mode", "regex") # regex, sequence, episode include_folders = request.args.get("include_folders", "false") == "true" filterwords = request.args.get("filterwords", "") - + account_index = int(request.args.get("account_index", 0)) # 新增账号索引参数 + if not pattern: pattern = ".*" - + try: + # 验证账号索引 + if account_index < 0 or account_index >= len(config_data["cookie"]): + return jsonify({"success": False, "message": "账号索引无效"}) + # 初始化夸克网盘客户端 - account = Quark(config_data["cookie"][0], 0) + account = Quark(config_data["cookie"][account_index], account_index) # 获取文件列表 if folder_id == "root": @@ -1415,13 +1616,18 @@ def batch_rename(): # 获取请求参数 data = request.json files = data.get("files", []) - + account_index = int(data.get("account_index", 0)) # 新增账号索引参数 + if not files: return jsonify({"success": False, "message": "没有文件需要重命名"}) - + try: + # 验证账号索引 + if account_index < 0 or account_index >= len(config_data["cookie"]): + return jsonify({"success": False, "message": "账号索引无效"}) + # 初始化夸克网盘客户端 - account = Quark(config_data["cookie"][0], 0) + account = Quark(config_data["cookie"][account_index], account_index) # 批量重命名 success_count = 0 @@ -1481,10 +1687,17 @@ def undo_rename(): return jsonify({"success": False, "message": "未登录"}) data = request.json save_path = data.get("save_path", "") + account_index = int(data.get("account_index", 0)) # 新增账号索引参数 + if not save_path: return jsonify({"success": False, "message": "缺少目录参数"}) + try: - account = Quark(config_data["cookie"][0], 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) # 查询该目录下最近一次重命名(按transfer_time分组,task_name=rename) records = record_db.get_records_by_save_path(save_path) rename_records = [r for r in records if r["task_name"] == "rename"] diff --git a/app/static/css/main.css b/app/static/css/main.css index 02f5743..50a8d7c 100644 --- a/app/static/css/main.css +++ b/app/static/css/main.css @@ -4037,6 +4037,21 @@ table.selectable-records .expand-button:hover { margin-right: -4px !important; } +/* 文件整理性能设置样式 */ +.performance-setting-row > [class*='col-'] { + padding-left: 4px !important; + padding-right: 4px !important; +} +.performance-setting-row { + margin-left: -4px !important; + margin-right: -4px !important; +} + +/* 性能设置板块标题上移8px,统一与其他设置板块的间距 */ +.row.title[title*="文件整理页面的请求参数"] { + margin-top: 12px !important; /* 从默认的20px减少到12px,上移8px */ +} + /* 任务单元基础样式 */ .task { position: relative; @@ -4187,6 +4202,19 @@ select.task-filter-select, .batch-rename-btn { margin-left: 8px; + color: var(--dark-text-color) !important; + border-color: var(--dark-text-color) !important; +} + +.batch-rename-btn:hover { + background-color: var(--dark-text-color) !important; + border-color: var(--dark-text-color) !important; + color: white !important; +} + +/* 文件整理页面刷新当前目录缓存按钮图标大小 */ +.batch-rename-btn .bi-arrow-clockwise { + font-size: 1.15rem; } /* 文件表格中的展开按钮 */ @@ -4422,6 +4450,28 @@ tr.selected-file .file-size-cell .delete-record-btn { width: 100%; } +/* 文件整理账号选择栏样式 */ +.file-manager-account-selector { + margin-bottom: 8px !important; +} + +/* 账号选择下拉框样式 */ +.file-manager-account-select { + padding-left: 8px !important; + text-indent: 0 !important; + display: flex !important; + align-items: center !important; + line-height: 1.5 !important; + padding-right: 24px !important; +} + +/* 账号选择框右侧圆角 - 需要更高优先级覆盖file-manager-input的圆角设置 */ +.file-manager-rule-bar .file-manager-account-select, +.file-manager-rule-bar-responsive .file-manager-account-select { + border-top-right-radius: 6px !important; + border-bottom-right-radius: 6px !important; +} + /* 禁止在表格中选择文本,以便更好地支持点击选择 */ table.selectable-files { user-select: none; @@ -4860,6 +4910,12 @@ body .selectable-files tr.selected-file .file-size-cell .delete-record-btn { border-radius: 0 !important; /* 去除圆角 */ } +/* 账号选择框例外 - 保持右侧圆角 */ +.file-manager-rule-bar .file-manager-input.file-manager-account-select { + border-top-right-radius: 6px !important; + border-bottom-right-radius: 6px !important; +} + /* 文件整理页面重命名配置框输入框相邻边框重叠样式 */ .file-manager-rule-bar .file-manager-input:not(:first-child) { margin-left: -1px; /* 向左移动1px,使边框重叠 */ @@ -5033,6 +5089,15 @@ body .selectable-files tr.selected-file .file-size-cell .delete-record-btn { .file-manager-rule-bar-responsive { display: block !important; } .file-manager-rule-bar-responsive .input-group { width: 100%; } .file-manager-rule-bar-responsive .input-group + .input-group { margin-top: 8px; } + + /* 移动端账号选择栏下边距 - 确保与命名规则栏有8px间距 */ + .file-manager-account-selector { + margin-bottom: 8px !important; + } + + .file-manager-account-selector .file-manager-rule-bar-responsive { + margin-bottom: 0 !important; /* 移除内部边距,使用外部容器的边距 */ + } /* 含文件夹样式调整 */ .file-manager-rule-bar-responsive .input-group-text.file-folder-rounded { border-top-left-radius: 0 !important; /* 左侧不要圆角 */ @@ -5067,6 +5132,11 @@ body .selectable-files tr.selected-file .file-size-cell .delete-record-btn { .file-manager-rule-bar-responsive .input-group .batch-rename-btn { margin-left: 8px !important; } + + /* 移动端账号选择栏中的刷新按钮间距 */ + .file-manager-rule-bar-responsive .d-flex .batch-rename-btn { + margin-left: 8px !important; + } } @media (min-width: 768px) { .file-manager-rule-bar { display: flex !important; } diff --git a/app/templates/index.html b/app/templates/index.html index fed8fb2..771bcc6 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -363,7 +363,7 @@ -

所有账号都会进行签到(纯签到只需填写移动端参数),只有第一个账号会进行转存,请自行确认顺序。如需签到,请在 Cookie 后方添加签到参数。

+

所有账号都会进行签到(纯签到只需填写移动端参数),只有第一个账号会进行转存,请自行确认账号顺序;所有填写了 Cookie 的账号均支持文件整理,如需签到请在 Cookie 后方添加签到参数。

@@ -398,7 +398,7 @@
-
+
延迟执行
@@ -612,6 +612,40 @@
+ +
+
+

性能设置

+ + + +
+
+
+
+
+
+ 单次请求文件数量 +
+ +
+ +
+
+
+
+
+
+ 文件列表缓存时长 +
+ +
+ +
+
+
+
+
@@ -1000,13 +1034,48 @@
+ +
- @@ -1022,7 +1091,7 @@  含文件夹
-
@@ -1031,7 +1100,7 @@
- @@ -1051,7 +1120,7 @@  含文件夹 -
@@ -1417,9 +1486,14 @@ delete_task: "always", refresh_plex: "always", refresh_alist: "always" + }, + file_performance: { + api_page_size: 200, + cache_expire_time: 30 } }, userInfoList: [], // 用户信息列表 + accountsDetail: [], // 账号详细信息列表 newTask: { taskname: "", shareurl: "", @@ -1497,8 +1571,13 @@ lastSelectedRecordIndex: -1, // 记录最后选择的记录索引,用于Shift选择 fileManager: { loading: false, - // 当前文件夹ID - 根据localStorage决定初始目录 - currentFolder: localStorage.getItem('quarkAutoSave_fileManagerLastFolder') || 'root', + // 当前文件夹ID - 根据localStorage和账号索引决定初始目录 + currentFolder: (() => { + const selectedAccountIndex = parseInt(localStorage.getItem('quarkAutoSave_selectedAccountIndex') || '0'); + return localStorage.getItem(`quarkAutoSave_fileManagerLastFolder_${selectedAccountIndex}`) || 'root'; + })(), + // 选中的账号索引 - 从localStorage读取,默认为0 + selectedAccountIndex: parseInt(localStorage.getItem('quarkAutoSave_selectedAccountIndex') || '0'), // 面包屑导航路径 paths: [], // 文件列表 @@ -1691,6 +1770,7 @@ activeTab(newValue, oldValue) { // 如果切换到文件整理页面,则加载文件列表 if (newValue === 'filemanager') { + this.fetchAccountsDetail(); this.loadFileListWithoutLoading(this.fileManager.currentFolder); } }, @@ -1705,7 +1785,11 @@ this.fetchData(); this.checkNewVersion(); this.fetchUserInfo(); // 获取用户信息 - + this.fetchAccountsDetail(); // 获取账号详细信息 + + // 迁移旧的localStorage数据到新格式(为每个账号单独存储目录) + this.migrateFileManagerFolderData(); + // 添加点击事件监听 document.addEventListener('click', this.handleOutsideClick); document.addEventListener('click', this.handleModalOutsideClick); @@ -1822,6 +1906,7 @@ // 如果当前标签是文件整理,则加载文件列表 if (this.activeTab === 'filemanager') { + this.fetchAccountsDetail(); this.loadFileListWithoutLoading(this.fileManager.currentFolder); } @@ -1874,6 +1959,92 @@ console.error('获取用户信息失败:', error); }); }, + + fetchAccountsDetail() { + // 获取所有账号的详细信息,包括空间使用情况 + axios.get('/get_accounts_detail') + .then(response => { + if (response.data.success) { + this.accountsDetail = response.data.data; + // 验证当前选中的账号索引是否有效 + if (this.fileManager.selectedAccountIndex >= this.accountsDetail.length) { + this.fileManager.selectedAccountIndex = 0; + localStorage.setItem('quarkAutoSave_selectedAccountIndex', '0'); + } + } + }) + .catch(error => { + console.error('获取账号详细信息失败:', error); + }); + }, + + // 迁移旧的localStorage数据到新格式(为每个账号单独存储目录) + migrateFileManagerFolderData() { + const oldFolderKey = 'quarkAutoSave_fileManagerLastFolder'; + const oldFolderValue = localStorage.getItem(oldFolderKey); + + if (oldFolderValue) { + // 如果存在旧数据,将其迁移到当前选中账号的新格式 + const currentAccountIndex = this.fileManager.selectedAccountIndex; + const newFolderKey = `quarkAutoSave_fileManagerLastFolder_${currentAccountIndex}`; + + // 只有当新格式的数据不存在时才进行迁移 + if (!localStorage.getItem(newFolderKey)) { + localStorage.setItem(newFolderKey, oldFolderValue); + console.log(`已将文件管理器目录数据迁移到账号 ${currentAccountIndex}: ${oldFolderValue}`); + } + + // 删除旧的数据 + localStorage.removeItem(oldFolderKey); + } + }, + + onAccountChange() { + // 保存选中的账号索引到localStorage + localStorage.setItem('quarkAutoSave_selectedAccountIndex', this.fileManager.selectedAccountIndex.toString()); + + // 获取新账号最后一次访问的目录 + const newAccountLastFolder = localStorage.getItem(`quarkAutoSave_fileManagerLastFolder_${this.fileManager.selectedAccountIndex}`) || 'root'; + + // 重置页码并切换到新账号的最后访问目录 + this.fileManager.currentPage = 1; + this.loadFileListWithFallback(newAccountLastFolder); + }, + + refreshCurrentFolderCache() { + // 刷新当前目录的缓存,强制重新请求最新的文件列表 + // 调用后端接口,添加强制刷新参数 + const params = { + folder_id: this.fileManager.currentFolder || 'root', + sort_by: this.fileManager.sortBy, + order: this.fileManager.sortOrder, + page_size: this.fileManager.pageSize, + page: this.fileManager.currentPage, + account_index: this.fileManager.selectedAccountIndex, + force_refresh: true // 强制刷新参数 + }; + + axios.get('/file_list', { params }) + .then(response => { + 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; + // 移除成功通知 + } else { + this.showToast('刷新失败:' + response.data.message); + } + + // 检测当前的命名模式 + this.detectFileManagerNamingMode(); + }) + .catch(error => { + console.error('刷新缓存失败:', error); + this.showToast('刷新缓存失败,请稍后重试'); + }); + }, // 添加一个检查分享链接状态的方法 checkShareUrlStatus() { // 只在任务列表页面检查 @@ -1966,6 +2137,8 @@ if (savedFileManagerPageSize) { this.fileManager.pageSize = savedFileManagerPageSize === 'all' ? 99999 : parseInt(savedFileManagerPageSize); } + // 获取账号详细信息 + this.fetchAccountsDetail(); this.loadFileListWithoutLoading(this.fileManager.currentFolder); } }, @@ -2042,6 +2215,24 @@ refresh_alist: "always" }; } + // 确保文件整理性能配置存在 + if (!config_data.file_performance) { + config_data.file_performance = { + api_page_size: 200, + cache_expire_time: 30 + }; + } else { + // 确保必要的字段存在,移除废弃的字段 + if (!config_data.file_performance.api_page_size) { + config_data.file_performance.api_page_size = 200; + } + if (!config_data.file_performance.cache_expire_time) { + config_data.file_performance.cache_expire_time = 30; + } + // 移除废弃的字段 + delete config_data.file_performance.large_page_size; + delete config_data.file_performance.cache_cleanup_interval; + } this.formData = config_data; setTimeout(() => { this.configModified = false; @@ -2869,6 +3060,22 @@ params = { fid: params } } this.modalLoading = true; + + // 根据模态框类型决定使用哪个账号 + // 任务配置相关的模态框始终使用主账号(索引0) + // 文件整理页面的预览模态框使用选中的账号 + const modalType = document.getElementById('fileSelectModal').getAttribute('data-modal-type'); + const accountIndex = (modalType === 'preview-filemanager') ? this.fileManager.selectedAccountIndex : 0; + + // 添加账号索引参数 + if (typeof params === 'object' && params !== null) { + params.account_index = accountIndex; + } else { + params = { + fid: params, + account_index: accountIndex + }; + } axios.get('/get_savepath_detail', { params: params }).then(response => { @@ -4090,7 +4297,7 @@ // 查找对应的文件对象,获取文件名 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, deleted_records: response.data.deleted_records || 0 }; @@ -4272,8 +4479,8 @@ } } - // 保存当前目录到localStorage - localStorage.setItem('quarkAutoSave_fileManagerLastFolder', folderId); + // 保存当前目录到localStorage(为每个账号单独保存) + localStorage.setItem(`quarkAutoSave_fileManagerLastFolder_${this.fileManager.selectedAccountIndex}`, folderId); // 重置页码并加载新文件夹内容,不显示加载动画 this.fileManager.currentPage = 1; @@ -4445,15 +4652,18 @@ files: [{ file_id: file.fid, new_name: newName - }] + }], + // 添加账号索引参数,使用文件整理页面选中的账号 + account_index: this.fileManager.selectedAccountIndex }) .then(response => { if (response.data.success) { this.showToast('重命名成功'); - // 更新本地文件列表中的文件名 - const index = this.fileManager.fileList.findIndex(f => f.fid === file.fid); - if (index !== -1) { - this.fileManager.fileList[index].file_name = newName; + // 刷新文件列表以确保缓存同步 + this.refreshCurrentFolderCache(); + // 如果命名预览模态框是打开的,也要刷新它 + if ($('#fileSelectModal').hasClass('show')) { + this.showFileManagerNamingPreview(this.fileManager.currentFolder); } } else { alert(response.data.message || '重命名失败'); @@ -4465,13 +4675,13 @@ }); } }, - switchNamingMode() { - // 不再切换命名模式,而是显示命名预览模态框 + previewAndRename() { + // 显示文件整理页面的命名预览模态框 this.showFileManagerNamingPreview(); }, showFileManagerNamingPreview(folderId) { - // 使用文件选择模态框,但设置为预览模式 + // 显示文件整理页面的命名预览模态框(区别于任务配置的命名预览) this.fileSelect.selectShare = false; this.fileSelect.selectDir = true; this.fileSelect.previewRegex = true; @@ -4510,7 +4720,9 @@ include_folders: this.fileManager.include_folders, filterwords: this.fileManager.filterwords, // 添加页面大小参数,获取所有文件 - page_size: 99999 + page_size: 99999, + // 添加账号索引参数,使用文件整理页面选中的账号 + account_index: this.fileManager.selectedAccountIndex } }) .then(response => { @@ -4528,7 +4740,11 @@ page_size: 99999, page: 1, // 同时添加文件夹包含参数 - include_folders: this.fileManager.include_folders + include_folders: this.fileManager.include_folders, + // 添加账号索引参数,使用文件整理页面选中的账号 + account_index: this.fileManager.selectedAccountIndex, + // 强制刷新缓存 + force_refresh: true } }) .then(listResponse => { @@ -4599,85 +4815,8 @@ this.modalLoading = false; }); }, - showBatchRenameModal() { - this.batchRename.loading = true; - this.batchRename.results = []; - this.batchRename.errors = []; - - // 设置命名模式 - if (this.fileManager.use_sequence_naming) { - this.batchRename.namingMode = 'sequence'; - } else if (this.fileManager.use_episode_naming) { - this.batchRename.namingMode = 'episode'; - } else { - this.batchRename.namingMode = 'regex'; - } - - // 调用后端API获取预览结果 - const params = { - folder_id: this.fileManager.currentFolder, - pattern: this.fileManager.pattern, - replace: this.fileManager.replace, - naming_mode: this.batchRename.namingMode, - include_folders: this.fileManager.include_folders, - filterwords: this.fileManager.filterwords - }; - - axios.get('/preview_rename', { params }) - .then(response => { - if (response.data.success) { - this.batchRename.files = response.data.data.map(file => ({ - oldName: file.original_name, - newName: file.new_name, - fileId: file.file_id - })); - } else { - alert(response.data.message || '预览重命名失败'); - } - this.batchRename.loading = false; - $('#batchRenameModal').modal('show'); - }) - .catch(error => { - console.error('预览重命名失败:', error); - alert('预览重命名失败,请检查命名规则是否正确'); - this.batchRename.loading = false; - }); - }, - confirmBatchRename() { - this.batchRename.loading = true; - - // 发送批量重命名请求 - const params = { - folder_id: this.fileManager.currentFolder, - pattern: this.fileManager.pattern, - replace: this.fileManager.replace, - naming_mode: this.batchRename.namingMode, - include_folders: this.fileManager.include_folders, - filterwords: this.fileManager.filterwords, - files: this.batchRename.files.filter(file => file.newName).map(file => ({ - file_id: file.fileId, - new_name: file.newName - })) - }; - - axios.post('/batch_rename', params) - .then(response => { - if (response.data.success) { - this.showToast('批量重命名成功'); - $('#batchRenameModal').modal('hide'); - // 刷新文件列表 - this.loadFileList(this.fileManager.currentFolder); - } else { - alert(response.data.message || '批量重命名失败'); - } - this.batchRename.loading = false; - }) - .catch(error => { - console.error('批量重命名失败:', error); - alert('批量重命名失败'); - this.batchRename.loading = false; - }); - }, + // showBatchRenameModal方法已删除,功能已整合到showFileManagerNamingPreview中 + // confirmBatchRename方法已删除,功能已整合到applyPreviewRename中 deleteFile(file) { if (confirm(`确定要删除${file.dir ? '文件夹' : '文件'} "${file.file_name}" 吗?`)) { // 获取当前路径作为save_path参数 @@ -4686,21 +4825,22 @@ save_path = this.fileManager.paths.map(item => item.name).join("/"); } - axios.post('/delete_file', { - fid: file.fid, - file_name: file.file_name, - delete_records: false, - save_path: save_path + axios.post('/delete_file', { + fid: file.fid, + file_name: file.file_name, + delete_records: false, + save_path: save_path, + account_index: this.fileManager.selectedAccountIndex }) .then(response => { if (response.data.code === 0) { this.showToast('成功删除 1 个项目'); - // 从列表中移除已删除的文件 - this.fileManager.fileList = this.fileManager.fileList.filter(f => f.fid !== file.fid); - // 更新总数 - this.fileManager.total -= 1; - // 从选中列表中移除 - this.fileManager.selectedFiles = this.fileManager.selectedFiles.filter(id => id !== file.fid); + // 刷新文件列表以确保缓存同步 + this.refreshCurrentFolderCache(); + // 如果命名预览模态框是打开的,也要刷新它 + if ($('#fileSelectModal').hasClass('show')) { + this.showFileManagerNamingPreview(this.fileManager.currentFolder); + } } else { alert(response.data.message || '删除失败'); } @@ -4714,13 +4854,14 @@ loadFileList(folderId) { this.fileManager.loading = true; this.fileManager.currentFolder = folderId || 'root'; - + const params = { folder_id: folderId || 'root', sort_by: this.fileManager.sortBy, order: this.fileManager.sortOrder, page_size: this.fileManager.pageSize, - page: this.fileManager.currentPage + page: this.fileManager.currentPage, + account_index: this.fileManager.selectedAccountIndex }; axios.get('/file_list', { params }) @@ -4795,8 +4936,8 @@ } } - // 保存当前目录到localStorage - localStorage.setItem('quarkAutoSave_fileManagerLastFolder', folderId); + // 保存当前目录到localStorage(为每个账号单独保存) + localStorage.setItem(`quarkAutoSave_fileManagerLastFolder_${this.fileManager.selectedAccountIndex}`, folderId); // 重置页码并加载新文件夹内容,不显示加载动画 this.fileManager.currentPage = 1; @@ -4806,22 +4947,23 @@ // 添加一个不显示加载动画的文件列表加载方法 loadFileListWithoutLoading(folderId) { this.fileManager.currentFolder = folderId || 'root'; - + // 从localStorage读取分页大小设置(仅在初始化时使用) // 在changeFileManagerPageSize方法中,我们已经确保了正确的执行顺序 const savedFileManagerPageSize = localStorage.getItem('quarkAutoSave_fileManagerPageSize'); if (savedFileManagerPageSize && this.fileManager.pageSize === 15) { // 只在默认值时读取 this.fileManager.pageSize = savedFileManagerPageSize === 'all' ? 99999 : parseInt(savedFileManagerPageSize); } - + const params = { folder_id: folderId || 'root', sort_by: this.fileManager.sortBy, order: this.fileManager.sortOrder, page_size: this.fileManager.pageSize, - page: this.fileManager.currentPage + page: this.fileManager.currentPage, + account_index: this.fileManager.selectedAccountIndex }; - + axios.get('/file_list', { params }) .then(response => { if (response.data.success) { @@ -4833,7 +4975,7 @@ } else { console.error('获取文件列表失败:', response.data.message); } - + // 检测当前的命名模式 this.detectFileManagerNamingMode(); }) @@ -4841,6 +4983,55 @@ console.error('获取文件列表失败:', error); }); }, + + // 添加带fallback机制的文件列表加载方法,用于账号切换 + loadFileListWithFallback(folderId) { + this.fileManager.currentFolder = folderId || 'root'; + + const params = { + folder_id: folderId || 'root', + sort_by: this.fileManager.sortBy, + order: this.fileManager.sortOrder, + page_size: this.fileManager.pageSize, + page: this.fileManager.currentPage, + account_index: this.fileManager.selectedAccountIndex + }; + + axios.get('/file_list', { params }) + .then(response => { + 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 { + // 如果获取文件列表失败,可能是目录在新账号中不存在 + console.warn('当前目录在新账号中不存在,自动切换到根目录:', response.data.message); + this.fallbackToRoot(); + } + }) + .catch(error => { + console.error('获取文件列表失败,自动切换到根目录:', error); + this.fallbackToRoot(); + }); + }, + + // fallback到根目录的方法 + fallbackToRoot() { + this.fileManager.currentFolder = 'root'; + this.fileManager.paths = []; + this.fileManager.currentPage = 1; + + // 更新localStorage中的当前目录(为每个账号单独保存) + localStorage.setItem(`quarkAutoSave_fileManagerLastFolder_${this.fileManager.selectedAccountIndex}`, 'root'); + + // 重新加载根目录 + this.loadFileListWithoutLoading('root'); + }, getVisibleFolderPageNumbers() { const current = parseInt(this.fileManager.currentPage) || 1; const total = parseInt(this.fileManager.totalPages) || 1; @@ -4876,47 +5067,7 @@ return range; }, - cancelBatchRename() { - this.batchRename.loading = false; - this.batchRename.results = []; - this.batchRename.errors = []; - $('#batchRenameModal').modal('hide'); - }, - applyBatchRename() { - this.batchRename.loading = true; - - // 发送批量重命名请求 - const params = { - folder_id: this.fileManager.currentFolder, - pattern: this.fileManager.pattern, - replace: this.fileManager.replace, - naming_mode: this.batchRename.namingMode, - include_folders: this.fileManager.include_folders, - filterwords: this.fileManager.filterwords, - files: this.batchRename.files.filter(file => file.newName).map(file => ({ - file_id: file.fileId, - new_name: file.newName - })) - }; - - axios.post('/batch_rename', params) - .then(response => { - if (response.data.success) { - this.showToast('批量重命名成功'); - $('#batchRenameModal').modal('hide'); - // 刷新文件列表 - this.loadFileList(this.fileManager.currentFolder); - } else { - alert(response.data.message || '批量重命名失败'); - } - this.batchRename.loading = false; - }) - .catch(error => { - console.error('批量重命名失败:', error); - alert('批量重命名失败'); - this.batchRename.loading = false; - }); - }, + // cancelBatchRename和applyBatchRename方法已删除,功能已整合到fileSelectModal中 handleFileManagerOutsideClick(event) { // 如果当前不是文件整理页面或者没有选中的文件,则不处理 if (this.activeTab !== 'filemanager' || this.fileManager.selectedFiles.length === 0) { @@ -4969,11 +5120,12 @@ const fileObj = this.fileManager.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 + return axios.post('/delete_file', { + fid: fid, + file_name: fileName, + delete_records: deleteRecords, + save_path: save_path, + account_index: this.fileManager.selectedAccountIndex }) .then(response => { return { fid: fid, success: response.data.code === 0, deleted_records: response.data.deleted_records || 0 }; @@ -4990,22 +5142,21 @@ 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.fileManager.fileList = this.fileManager.fileList.filter(item => !successfullyDeletedFids.includes(item.fid)); - - // 更新总数 - this.fileManager.total -= successCount; - - // 清空选中文件列表 - this.fileManager.selectedFiles = []; - // 显示结果 if (failCount > 0) { alert(`成功删除 ${successCount} 个项目,${failCount} 个项目删除失败`); } else { this.showToast(`成功删除 ${successCount} 个项目`); } + + // 如果有成功删除的文件,刷新文件列表以确保缓存同步 + if (successCount > 0) { + this.refreshCurrentFolderCache(); + // 如果命名预览模态框是打开的,也要刷新它 + if ($('#fileSelectModal').hasClass('show')) { + this.showFileManagerNamingPreview(this.fileManager.currentFolder); + } + } }); } }, @@ -5056,10 +5207,16 @@ old_name: f.file_name // 传递原文件名,便于撤销 })); const save_path = this.fileManager.currentFolder; - const response = await axios.post('/batch_rename', { files, save_path }); + const response = await axios.post('/batch_rename', { + files, + save_path, + // 添加账号索引参数,使用文件整理页面选中的账号 + account_index: this.fileManager.selectedAccountIndex + }); if (response.data.success) { this.showToast(`成功重命名 ${response.data.success_count || files.length} 个项目`); - this.loadFileListWithoutLoading(this.fileManager.currentFolder); + // 强制刷新文件列表缓存 + this.refreshCurrentFolderCache(); $('#fileSelectModal').modal('hide'); this.fileSelect.canUndoRename = true; // 重命名后立即可撤销 } else { @@ -5075,11 +5232,16 @@ this.modalLoading = true; try { const save_path = this.fileManager.currentFolder; - const response = await axios.post('/undo_rename', { save_path }); + const response = await axios.post('/undo_rename', { + save_path, + // 添加账号索引参数,使用文件整理页面选中的账号 + account_index: this.fileManager.selectedAccountIndex + }); if (response.data.success) { this.showToast(`成功撤销 ${response.data.success_count} 个项目重命名`); this.showFileManagerNamingPreview(save_path); - this.loadFileListWithoutLoading(this.fileManager.currentFolder); + // 强制刷新文件列表缓存 + this.refreshCurrentFolderCache(); // 撤销后如无可撤销项再设为false(由showFileManagerNamingPreview刷新) } else { this.showToast(response.data.message || '撤销失败'); diff --git a/quark_auto_save.py b/quark_auto_save.py index 83cbf69..21cfabb 100644 --- a/quark_auto_save.py +++ b/quark_auto_save.py @@ -932,6 +932,9 @@ class Quark: def ls_dir(self, pdir_fid, **kwargs): file_list = [] page = 1 + # 优化:增加每页大小,减少API调用次数 + page_size = kwargs.get("page_size", 200) # 从50增加到200 + while True: url = f"{self.BASE_URL}/1/clouddrive/file/sort" querystring = { @@ -940,7 +943,7 @@ class Quark: "uc_param_str": "", "pdir_fid": pdir_fid, "_page": page, - "_size": "50", + "_size": str(page_size), "_fetch_total": "1", "_fetch_sub_dirs": "0", "_sort": "file_type:asc,updated_at:desc",