diff --git a/README.md b/README.md index 155e479..1e55ef3 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ - **查重逻辑**:支持优先通过历史转存记录查重,对于有转存记录的文件,即使删除网盘文件,也不会重复转存。 - **Aria2**:支持成功添加 Aria2 下载任务后自动删除夸克网盘内对应的文件,清理网盘空间。 - **文件整理**:支持浏览和管理多个夸克账号的网盘文件,支持单项/批量重命名(支持应用完整的命名、过滤规则和撤销重命名等操作)、移动文件、删除文件、新建文件夹等操作。 +- **更新状态**:支持在任务列表页面显示任务的最近更新日期、最近转存文件,支持在任务列表、转存记录、文件整理页面显示当日更新标识(对于当日更新的内容)。 本项目修改后的版本为个人需求定制版,目的是满足我自己的使用需求,某些(我不用的)功能可能会因为修改而出现 BUG,不一定会被修复。若你要使用本项目,请知晓本人不是程序员,我无法保证本项目的稳定性,如果你在使用过程中发现了 BUG,可以在 Issues 中提交,但不保证每个 BUG 都能被修复,请谨慎使用,风险自担。 diff --git a/app/run.py b/app/run.py index be2f470..d63b749 100644 --- a/app/run.py +++ b/app/run.py @@ -40,6 +40,35 @@ from quark_auto_save import Config, format_bytes sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from quark_auto_save import extract_episode_number, sort_file_by_name, chinese_to_arabic, is_date_format + +def process_season_episode_info(filename): + """ + 处理文件名中的季数和集数信息 + + Args: + filename: 文件名(不含扩展名) + + Returns: + 处理后的显示名称 + """ + # 匹配 SxxExx 格式(不区分大小写) + # 支持 S1E1, S01E01, s13e10 等格式 + season_episode_match = re.search(r'[Ss](\d{1,2})[Ee](\d{1,3})', filename) + if season_episode_match: + season = season_episode_match.group(1).zfill(2) # 确保两位数 + episode = season_episode_match.group(2).zfill(2) # 确保两位数 + return f"S{season}E{episode}" + + # 匹配只有 Exx 或 EPxx 格式(不区分大小写) + # 支持 E1, E01, EP1, EP01, e10, ep10 等格式 + episode_only_match = re.search(r'[Ee][Pp]?(\d{1,3})', filename) + if episode_only_match: + episode = episode_only_match.group(1).zfill(2) # 确保两位数 + return f"E{episode}" + + # 如果没有匹配到季数集数信息,返回原文件名 + return filename + # 导入拼音排序工具 try: from utils.pinyin_sort import get_filename_pinyin_sort_key @@ -1358,12 +1387,118 @@ def delete_history_records(): deleted_count += db.delete_record(record_id) return jsonify({ - "success": True, + "success": True, "message": f"成功删除 {deleted_count} 条记录", "deleted_count": deleted_count }) +# 获取任务最新转存信息(包括日期和文件) +@app.route("/task_latest_info") +def get_task_latest_info(): + if not is_login(): + return jsonify({"success": False, "message": "未登录"}) + + try: + # 初始化数据库 + db = RecordDB() + cursor = db.conn.cursor() + + # 获取所有任务的最新转存时间 + query = """ + SELECT task_name, MAX(transfer_time) as latest_transfer_time + FROM transfer_records + WHERE task_name != 'rename' + GROUP BY task_name + """ + cursor.execute(query) + latest_times = cursor.fetchall() + + task_latest_records = {} # 存储最新转存日期 + task_latest_files = {} # 存储最新转存文件 + + for task_name, latest_time in latest_times: + if latest_time: + # 1. 处理最新转存日期 + try: + # 确保时间戳在合理范围内 + timestamp = int(latest_time) + if timestamp > 9999999999: # 检测是否为毫秒级时间戳(13位) + timestamp = timestamp / 1000 # 转换为秒级时间戳 + + if 0 < timestamp < 4102444800: # 从1970年到2100年的合理时间戳范围 + # 格式化为月-日格式(用于显示)和完整日期(用于今日判断) + date_obj = datetime.fromtimestamp(timestamp) + formatted_date = date_obj.strftime("%m-%d") + full_date = date_obj.strftime("%Y-%m-%d") + task_latest_records[task_name] = { + "display": formatted_date, # 显示用的 MM-DD 格式 + "full": full_date # 比较用的 YYYY-MM-DD 格式 + } + except (ValueError, TypeError, OverflowError): + pass # 忽略无效的时间戳 + + # 2. 处理最新转存文件 + # 获取该任务在最新转存时间附近(同一分钟内)的所有文件 + # 这样可以处理同时转存多个文件但时间戳略有差异的情况 + time_window = 60000 # 60秒的时间窗口(毫秒) + query = """ + SELECT renamed_to, original_name, transfer_time, modify_date + FROM transfer_records + WHERE task_name = ? AND transfer_time >= ? AND transfer_time <= ? + ORDER BY id DESC + """ + cursor.execute(query, (task_name, latest_time - time_window, latest_time + time_window)) + files = cursor.fetchall() + + if files: + if len(files) == 1: + # 如果只有一个文件,直接使用 + best_file = files[0][0] # renamed_to + else: + # 如果有多个文件,使用全局排序函数进行排序 + file_list = [] + for renamed_to, original_name, transfer_time, modify_date in files: + # 构造文件信息字典,模拟全局排序函数需要的格式 + file_info = { + 'file_name': renamed_to, + 'original_name': original_name, + 'updated_at': transfer_time # 使用转存时间而不是文件修改时间 + } + file_list.append(file_info) + + # 使用全局排序函数进行正向排序,最后一个就是最新的 + try: + sorted_files = sorted(file_list, key=sort_file_by_name) + best_file = sorted_files[-1]['file_name'] # 取排序后的最后一个文件 + except Exception as e: + # 如果排序失败,使用第一个文件作为备选 + best_file = files[0][0] + + # 去除扩展名并处理季数集数信息 + if best_file: + file_name_without_ext = os.path.splitext(best_file)[0] + processed_name = process_season_episode_info(file_name_without_ext) + task_latest_files[task_name] = processed_name + + db.close() + + return jsonify({ + "success": True, + "data": { + "latest_records": task_latest_records, + "latest_files": task_latest_files + } + }) + + except Exception as e: + return jsonify({ + "success": False, + "message": f"获取任务最新信息失败: {str(e)}" + }) + + + # 删除单条转存记录 @app.route("/delete_history_record", methods=["POST"]) def delete_history_record(): diff --git a/app/static/css/main.css b/app/static/css/main.css index e2d2561..99bc8db 100644 --- a/app/static/css/main.css +++ b/app/static/css/main.css @@ -1678,7 +1678,7 @@ button.close:focus, } #fileSelectModal .table .text-warning { - color: #ffc107 !important; + color: #098eff !important; } /* 弹窗内表格行悬停效果 */ @@ -1687,7 +1687,20 @@ button.close:focus, cursor: pointer; } -#fileSelectModal .bi-file-earmark { +#fileSelectModal .bi-file-earmark, +#fileSelectModal .bi-file-earmark-play, +#fileSelectModal .bi-file-earmark-music, +#fileSelectModal .bi-file-earmark-image, +#fileSelectModal .bi-file-earmark-text, +#fileSelectModal .bi-file-earmark-richtext, +#fileSelectModal .bi-file-earmark-zip, +#fileSelectModal .bi-file-earmark-font, +#fileSelectModal .bi-file-earmark-code, +#fileSelectModal .bi-file-earmark-pdf, +#fileSelectModal .bi-file-earmark-word, +#fileSelectModal .bi-file-earmark-excel, +#fileSelectModal .bi-file-earmark-ppt, +#fileSelectModal .bi-file-earmark-medical { color: var(--dark-text-color); font-size: 0.9rem; margin-right: 5px; @@ -3915,7 +3928,7 @@ table.selectable-records .expand-button:hover { /* 模态框通用文件夹图标样式 */ #fileSelectModal .bi-folder-fill { - color: #ffc107; + color: #098eff; font-size: 0.95rem; margin-right: 4px !important; position: relative; @@ -3924,7 +3937,20 @@ table.selectable-records .expand-button:hover { } /* 模态框通用文件图标样式 */ -#fileSelectModal .bi-file-earmark { +#fileSelectModal .bi-file-earmark, +#fileSelectModal .bi-file-earmark-play, +#fileSelectModal .bi-file-earmark-music, +#fileSelectModal .bi-file-earmark-image, +#fileSelectModal .bi-file-earmark-text, +#fileSelectModal .bi-file-earmark-richtext, +#fileSelectModal .bi-file-earmark-zip, +#fileSelectModal .bi-file-earmark-font, +#fileSelectModal .bi-file-earmark-code, +#fileSelectModal .bi-file-earmark-pdf, +#fileSelectModal .bi-file-earmark-word, +#fileSelectModal .bi-file-earmark-excel, +#fileSelectModal .bi-file-earmark-ppt, +#fileSelectModal .bi-file-earmark-medical { color: var(--dark-text-color); font-size: 0.95rem; margin-right: 4px !important; @@ -3994,7 +4020,6 @@ table.selectable-records .expand-button:hover { /* 任务按钮悬停显示样式 */ .task-buttons .hover-only { opacity: 0; - transition: opacity 0.2s ease-in-out; position: absolute; right: 0; visibility: hidden; @@ -4029,24 +4054,92 @@ table.selectable-records .expand-button:hover { } } +/* 任务最近更新日期样式 */ +.task-latest-date { + color: var(--dark-text-color); + font-size: 0.95rem; + font-weight: normal; +} + +/* 任务最近转存文件样式 */ +.task-latest-file { + color: var(--dark-text-color); + font-size: 0.95rem; + font-weight: normal; +} + +/* 悬停显示模式 - 使用display来避免占用空间,同时保持平滑的悬停效果 */ +.task-latest-date.hover-only, +.task-latest-file.hover-only { + display: none; + transition: all 0.2s ease; +} + +.task .btn:hover .task-latest-date.hover-only, +.task .btn:hover .task-latest-file.hover-only { + display: inline; +} + +/* 显示设置行样式 */ +.display-setting-row > [class*='col-'] { + padding-left: 4px !important; + padding-right: 4px !important; +} + +/* 当天更新指示器样式 */ +.task-today-indicator { + display: inline-block; + font-size: 0.92rem; + font-weight: bold; +} + +.task-today-indicator i { + background: linear-gradient(45deg, #03d5ff 25%, var(--focus-border-color) 60%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + color: var(--focus-border-color); /* 备用颜色,以防渐变不支持 */ +} + +/* 当日更新图标悬停显示样式 */ +.task-today-indicator.hover-only { + opacity: 0; + visibility: hidden; +} + +/* 任务列表页面悬停显示 */ +.task:hover .task-today-indicator.hover-only { + opacity: 1; + visibility: visible; +} + +/* 转存记录页面悬停显示 */ +.table-hover tbody tr:hover .task-today-indicator.hover-only { + opacity: 1; + visibility: visible; +} + +/* 文件整理页面悬停显示 */ +.selectable-files tbody tr:hover .task-today-indicator.hover-only { + opacity: 1; + visibility: visible; +} + +.display-setting-row { + margin-left: -4px !important; + margin-right: -4px !important; +} + @media (min-width: 992px) { .display-setting-row > .col-lg-3 { padding-left: 4px !important; padding-right: 4px !important; } - .display-setting-row { - margin-left: -4px !important; - margin-right: -4px !important; - } } -.display-setting-row > [class*='col-'] { - padding-left: 4px !important; - padding-right: 4px !important; -} -.display-setting-row { - margin-left: -4px !important; - margin-right: -4px !important; +/* 调整显示设置第二行的上边距,使其与第一行保持8px间距 */ +.display-setting-row + .display-setting-row { + margin-top: -8px !important; } /* 文件整理性能设置样式 */ @@ -4075,22 +4168,6 @@ table.selectable-records .expand-button:hover { cursor: default; } -/* 任务按钮悬停显示样式 */ -.task-buttons .hover-only { - opacity: 0; - transition: opacity 0.2s ease-in-out; - position: absolute; - right: 0; - visibility: hidden; -} - -/* 修改悬停触发范围到整个任务单元 */ -.task:hover .task-buttons .hover-only { - opacity: 1; - visibility: visible; - position: static; -} - /* 确保按钮容器在悬停时保持宽度 */ .task-buttons { position: relative; @@ -5228,7 +5305,20 @@ body .selectable-files tr.selected-file:has([style*="white-space: normal"]) .fil } /* 文件整理页面的文件图标样式 */ -.selectable-files .bi-file-earmark { +.selectable-files .bi-file-earmark, +.selectable-files .bi-file-earmark-play, +.selectable-files .bi-file-earmark-music, +.selectable-files .bi-file-earmark-image, +.selectable-files .bi-file-earmark-text, +.selectable-files .bi-file-earmark-richtext, +.selectable-files .bi-file-earmark-zip, +.selectable-files .bi-file-earmark-font, +.selectable-files .bi-file-earmark-code, +.selectable-files .bi-file-earmark-pdf, +.selectable-files .bi-file-earmark-word, +.selectable-files .bi-file-earmark-excel, +.selectable-files .bi-file-earmark-ppt, +.selectable-files .bi-file-earmark-medical { font-size: 1.06rem; /* 比模态框的0.95rem大一些 */ margin-right: 7px !important; /* 图标距离文本的距离 */ position: relative; @@ -5243,7 +5333,7 @@ body .selectable-files tr.selected-file:has([style*="white-space: normal"]) .fil position: relative; top: 1px; /* 可微调垂直对齐 */ left: -1px; /* 可微调水平对齐 */ - color: #ffc107; /* 保持黄色 */ + color: #098eff; /* 55%接近深蓝色 */ } /* 文件整理页面无法识别剧集编号样式 */ diff --git a/app/templates/index.html b/app/templates/index.html index 347eede..643f4b7 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -69,18 +69,109 @@ } }); + // 根据文件扩展名获取对应的Bootstrap图标类名 + function getFileIconClass(fileName, isDir = false) { + // 如果是文件夹,返回文件夹图标 + if (isDir) { + return 'bi-folder-fill'; + } + + // 获取文件扩展名(转为小写) + const ext = fileName.toLowerCase().split('.').pop(); + + // 视频文件 + const videoExts = ['mp4', 'mkv', 'avi', 'mov', 'rmvb', 'flv', 'wmv', 'm4v', 'ts', 'webm', '3gp', 'f4v']; + if (videoExts.includes(ext)) { + return 'bi-file-earmark-play'; + } + + // 音频文件 + const audioExts = ['mp3', 'wav', 'flac', 'aac', 'ogg', 'm4a', 'wma', 'ape', 'ac3', 'dts']; + if (audioExts.includes(ext)) { + return 'bi-file-earmark-music'; + } + + // 图片文件 + const imageExts = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'tiff', 'svg', 'ico', 'raw']; + if (imageExts.includes(ext)) { + return 'bi-file-earmark-image'; + } + + // 文本文件(包括歌词文件和字幕文件) + const textExts = ['txt', 'md', 'rtf', 'log', 'ini', 'cfg', 'conf', 'lrc', 'srt', 'ass', 'ssa', 'vtt', 'sup']; + if (textExts.includes(ext)) { + return 'bi-file-earmark-text'; + } + + // 富文本文件 + const richtextExts = ['rtf', 'odt']; + if (richtextExts.includes(ext)) { + return 'bi-file-earmark-richtext'; + } + + // 压缩文件 + const archiveExts = ['zip', 'rar', '7z', 'tar', 'gz', 'bz2', 'xz', 'lzma', 'cab', 'iso']; + if (archiveExts.includes(ext)) { + return 'bi-file-earmark-zip'; + } + + // 字体文件 + const fontExts = ['ttf', 'otf', 'woff', 'woff2', 'eot']; + if (fontExts.includes(ext)) { + return 'bi-file-earmark-font'; + } + + // 代码文件 + const codeExts = ['js', 'html', 'css', 'py', 'java', 'c', 'cpp', 'php', 'go', 'json', 'xml', 'yml', 'yaml', 'sql', 'sh', 'bat', 'ps1', 'rb', 'swift', 'kt', 'ts', 'jsx', 'tsx', 'vue', 'scss', 'sass', 'less']; + if (codeExts.includes(ext)) { + return 'bi-file-earmark-code'; + } + + // PDF文件 + if (ext === 'pdf') { + return 'bi-file-earmark-pdf'; + } + + // Word文档 + const wordExts = ['doc', 'docx']; + if (wordExts.includes(ext)) { + return 'bi-file-earmark-word'; + } + + // Excel文档 + const excelExts = ['xls', 'xlsx', 'csv']; + if (excelExts.includes(ext)) { + return 'bi-file-earmark-excel'; + } + + // PowerPoint文档 + const pptExts = ['ppt', 'pptx']; + if (pptExts.includes(ext)) { + return 'bi-file-earmark-ppt'; + } + + // 医疗/健康相关文件 + const medicalExts = ['dcm', 'dicom', 'hl7']; + if (medicalExts.includes(ext)) { + return 'bi-file-earmark-medical'; + } + + // 默认文件图标 + return 'bi-file-earmark'; + } + // 添加检测文件整理页面文件名溢出的自定义指令 Vue.directive('check-file-overflow', { inserted: function(el, binding, vnode) { // 检查元素是否溢出 const isOverflowing = el.scrollWidth > el.clientWidth; - + // 如果绑定了值,则绑定到该值对应的文件属性上 if (binding.value) { const indexAndField = binding.value.split('|'); const index = parseInt(indexAndField[0]); const field = indexAndField[1]; - + // 设置文件的_isOverflowing属性 const files = vnode.context.fileManager.fileList; if (files && files[index]) { @@ -97,13 +188,13 @@ update: function(el, binding, vnode) { // 检查元素是否溢出 const isOverflowing = el.scrollWidth > el.clientWidth; - + // 如果绑定了值,则绑定到该值对应的文件属性上 if (binding.value) { const indexAndField = binding.value.split('|'); const index = parseInt(indexAndField[0]); const field = indexAndField[1]; - + // 设置文件的_isOverflowing属性 const files = vnode.context.fileManager.fileList; if (files && files[index]) { @@ -614,6 +705,44 @@ +