diff --git a/app/run.py b/app/run.py
index f4b16bd..2921619 100644
--- a/app/run.py
+++ b/app/run.py
@@ -591,6 +591,22 @@ def get_share_detail():
episode_pattern = regex.get("episode_naming")
episode_patterns = regex.get("episode_patterns", [])
+ # 获取默认的剧集模式
+ default_episode_pattern = {"regex": '第(\\d+)集|第(\\d+)期|第(\\d+)话|(\\d+)集|(\\d+)期|(\\d+)话|[Ee][Pp]?(\\d+)|(\\d+)[-_\\s]*4[Kk]|\\[(\\d+)\\]|【(\\d+)】|_?(\\d+)_?'}
+
+ # 获取配置的剧集模式,确保每个模式都是字典格式
+ episode_patterns = []
+ raw_patterns = config_data.get("episode_patterns", [default_episode_pattern])
+ for p in raw_patterns:
+ if isinstance(p, dict) and p.get("regex"):
+ episode_patterns.append(p)
+ elif isinstance(p, str):
+ episode_patterns.append({"regex": p})
+
+ # 如果没有有效的模式,使用默认模式
+ if not episode_patterns:
+ episode_patterns = [default_episode_pattern]
+
# 添加中文数字匹配模式
chinese_patterns = [
{"regex": r'第([一二三四五六七八九十百千万零两]+)集'},
@@ -600,74 +616,7 @@ def get_share_detail():
{"regex": r'([一二三四五六七八九十百千万零两]+)期'},
{"regex": r'([一二三四五六七八九十百千万零两]+)话'}
]
-
- # 合并中文模式到episode_patterns
- if episode_patterns:
- episode_patterns.extend(chinese_patterns)
- else:
- episode_patterns = chinese_patterns
-
- # 调用全局的集编号提取函数
- def extract_episode_number_local(filename):
- return extract_episode_number(filename, episode_patterns=episode_patterns)
-
- # 构建剧集命名的正则表达式 (主要用于检测已命名文件)
- if episode_pattern == "[]":
- # 对于单独的[],使用特殊匹配
- regex_pattern = "^(\\d+)$" # 匹配纯数字文件名
- elif "[]" in episode_pattern:
- # 特殊处理E[]、EP[]等常见格式,使用更宽松的匹配方式
- if episode_pattern == "E[]":
- # 对于E[]格式,只检查文件名中是否包含形如E01的部分
- regex_pattern = "^E(\\d+)$" # 只匹配纯E+数字的文件名格式
- elif episode_pattern == "EP[]":
- # 对于EP[]格式,只检查文件名中是否包含形如EP01的部分
- regex_pattern = "^EP(\\d+)$" # 只匹配纯EP+数字的文件名格式
- else:
- # 对于其他带[]的格式,使用常规转义和替换
- regex_pattern = re.escape(episode_pattern).replace('\\[\\]', '(\\d+)')
- else:
- # 如果输入模式不包含[],则使用简单匹配模式,避免正则表达式错误
- regex_pattern = "^" + re.escape(episode_pattern) + "(\\d+)$"
-
- # 实现高级排序算法
- def extract_sorting_value(file):
- if file["dir"]: # 跳过文件夹
- return (float('inf'), 0, 0, 0) # 返回元组以确保类型一致性
-
- filename = file["file_name"]
-
- # 尝试获取剧集序号
- episode_num = extract_episode_number_local(filename)
- if episode_num is not None:
- # 返回元组以确保类型一致性
- return (0, episode_num, 0, 0)
-
- # 如果无法提取剧集号,则使用通用的排序函数
- return sort_file_by_name(file)
-
- # 过滤出非目录文件,并且排除已经符合命名规则的文件
- files_to_process = []
- for f in share_detail["list"]:
- if f["dir"]:
- continue # 跳过目录
-
- # 检查文件是否已符合命名规则
- if episode_pattern == "[]":
- # 对于单独的[],检查文件名是否为纯数字
- file_name_without_ext = os.path.splitext(f["file_name"])[0]
- if file_name_without_ext.isdigit():
- # 增加判断:如果是日期格式的纯数字,不视为已命名
- if not is_date_format(file_name_without_ext):
- continue # 跳过已符合命名规则的文件
- elif re.match(regex_pattern, f["file_name"]):
- continue # 跳过已符合命名规则的文件
-
- # 添加到待处理文件列表
- files_to_process.append(f)
-
- # 根据提取的排序值进行排序
- sorted_files = sorted(files_to_process, key=extract_sorting_value)
+ episode_patterns.extend(chinese_patterns)
# 应用过滤词过滤
filterwords = regex.get("filterwords", "")
@@ -675,32 +624,25 @@ def get_share_detail():
# 同时支持中英文逗号分隔
filterwords = filterwords.replace(",", ",")
filterwords_list = [word.strip() for word in filterwords.split(',')]
- for item in sorted_files:
- # 被过滤的文件不会有file_name_re,与不匹配正则的文件显示一致
+ for item in share_detail["list"]:
+ # 被过滤的文件显示一个 ×
if any(word in item['file_name'] for word in filterwords_list):
item["filtered"] = True
+ item["file_name_re"] = "×"
- # 为每个文件生成新文件名并存储剧集编号用于排序
- for file in sorted_files:
- if not file.get("filtered"):
- # 获取文件扩展名
- file_ext = os.path.splitext(file["file_name"])[1]
- # 尝试提取剧集号
- episode_num = extract_episode_number_local(file["file_name"])
- if episode_num is not None:
- # 生成预览文件名
- if episode_pattern == "[]":
- # 对于单独的[],直接使用数字序号作为文件名
- file["file_name_re"] = f"{episode_num:02d}{file_ext}"
- else:
- file["file_name_re"] = episode_pattern.replace("[]", f"{episode_num:02d}") + file_ext
- # 存储原始的剧集编号,用于数值排序
- file["episode_number"] = episode_num
- else:
- # 无法提取剧集号,标记为无法处理
- file["file_name_re"] = "❌ 无法识别剧集号"
- file["episode_number"] = 9999999 # 给一个很大的值,确保排在最后
+ # 处理未被过滤的文件
+ for file in share_detail["list"]:
+ if not file["dir"] and not file.get("filtered"): # 只处理未被过滤的非目录文件
+ extension = os.path.splitext(file["file_name"])[1]
+ # 从文件名中提取集号
+ episode_num = extract_episode_number(file["file_name"], episode_patterns=episode_patterns)
+ if episode_num is not None:
+ file["file_name_re"] = episode_pattern.replace("[]", f"{episode_num:02d}") + extension
+ else:
+ # 没有提取到集号,显示无法识别的提示
+ file["file_name_re"] = "× 无法识别剧集编号"
+
return share_detail
else:
# 普通正则命名预览
@@ -990,7 +932,7 @@ def get_history_records():
# 如果请求所有任务名称,单独查询并返回
if get_all_task_names:
cursor = db.conn.cursor()
- cursor.execute("SELECT DISTINCT task_name FROM transfer_records ORDER BY task_name")
+ cursor.execute("SELECT DISTINCT task_name FROM transfer_records WHERE task_name NOT IN ('rename', 'undo_rename') ORDER BY task_name")
all_task_names = [row[0] for row in cursor.fetchall()]
# 如果同时请求分页数据,继续常规查询
@@ -1001,7 +943,8 @@ def get_history_records():
sort_by=sort_by,
order=order,
task_name_filter=task_name_filter,
- keyword_filter=keyword_filter
+ keyword_filter=keyword_filter,
+ exclude_task_names=["rename", "undo_rename"]
)
# 添加所有任务名称到结果中
result["all_task_names"] = all_task_names
@@ -1021,7 +964,8 @@ def get_history_records():
sort_by=sort_by,
order=order,
task_name_filter=task_name_filter,
- keyword_filter=keyword_filter
+ keyword_filter=keyword_filter,
+ exclude_task_names=["rename", "undo_rename"]
)
# 处理记录格式化
@@ -1233,7 +1177,371 @@ def reset_folder():
return jsonify({"success": False, "message": f"重置文件夹时出错: {str(e)}"})
+# 获取文件列表
+@app.route("/file_list")
+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))
+
+ try:
+ # 初始化夸克网盘客户端
+ account = Quark(config_data["cookie"][0], 0)
+
+ # 获取文件列表
+ if folder_id == "root":
+ folder_id = "0" # 根目录的ID为0
+ paths = [] # 根目录没有路径
+ else:
+ # 获取当前文件夹的路径
+ paths = account.get_paths(folder_id)
+
+ # 获取文件列表
+ files = account.ls_dir(folder_id)
+ if isinstance(files, dict) and files.get("error"):
+ return jsonify({"success": False, "message": f"获取文件列表失败: {files.get('error', '未知错误')}"})
+
+ # 计算总数
+ 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()
+
+ # 根据排序字段决定是否将目录放在前面
+ if sort_by == "updated_at":
+ # 修改日期排序时严格按照日期排序,不区分文件夹和文件
+ sorted_files = files
+ else:
+ # 其他排序时目录始终在前面
+ directories = [f for f in files if f["dir"]]
+ normal_files = [f for f in files if not f["dir"]]
+ sorted_files = directories + normal_files
+
+ # 分页
+ 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,
+ "data": {
+ "list": paginated_files,
+ "total": total,
+ "paths": paths
+ }
+ })
+
+ except Exception as e:
+ logging.error(f">>> 获取文件列表时出错: {str(e)}")
+ return jsonify({"success": False, "message": f"获取文件列表时出错: {str(e)}"})
+
+
+# 预览重命名
+@app.route("/preview_rename")
+def preview_rename():
+ if not is_login():
+ return jsonify({"success": False, "message": "未登录"})
+
+ # 获取请求参数
+ folder_id = request.args.get("folder_id", "root")
+ pattern = request.args.get("pattern", "")
+ replace = request.args.get("replace", "")
+ 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", "")
+
+ if not pattern:
+ pattern = ".*"
+
+ try:
+ # 初始化夸克网盘客户端
+ account = Quark(config_data["cookie"][0], 0)
+
+ # 获取文件列表
+ if folder_id == "root":
+ folder_id = "0" # 根目录的ID为0
+
+ files = account.ls_dir(folder_id)
+ if isinstance(files, dict) and files.get("error"):
+ return jsonify({"success": False, "message": f"获取文件列表失败: {files.get('error', '未知错误')}"})
+
+ # 过滤要排除的文件
+ # 替换中文逗号为英文逗号
+ filterwords = filterwords.replace(",", ",")
+ filter_list = [keyword.strip() for keyword in filterwords.split(",") if keyword.strip()]
+ filtered_files = []
+ for file in files:
+ # 如果不包含文件夹且当前项是文件夹,跳过
+ if not include_folders and file["dir"]:
+ continue
+
+ # 检查是否包含过滤关键词
+ should_filter = False
+ for keyword in filter_list:
+ if keyword and keyword in file["file_name"]:
+ should_filter = True
+ break
+
+ if not should_filter:
+ filtered_files.append(file)
+
+ # 按不同命名模式处理
+ preview_results = []
+
+ if naming_mode == "sequence":
+ # 顺序命名模式
+ # 排序文件(按文件名或修改时间)
+ filtered_files.sort(key=lambda x: sort_file_by_name(x["file_name"]))
+
+ sequence = 1
+ for file in filtered_files:
+ extension = os.path.splitext(file["file_name"])[1] if not file["dir"] else ""
+ new_name = pattern.replace("{}", f"{sequence:02d}") + extension
+ preview_results.append({
+ "original_name": file["file_name"],
+ "new_name": new_name,
+ "file_id": file["fid"]
+ })
+ sequence += 1
+
+ elif naming_mode == "episode":
+ # 剧集命名模式
+ # 获取默认的剧集模式
+ default_episode_pattern = {"regex": '第(\\d+)集|第(\\d+)期|第(\\d+)话|(\\d+)集|(\\d+)期|(\\d+)话|[Ee][Pp]?(\\d+)|(\\d+)[-_\\s]*4[Kk]|\\[(\\d+)\\]|【(\\d+)】|_?(\\d+)_?'}
+
+ # 获取配置的剧集模式,确保每个模式都是字典格式
+ episode_patterns = []
+ raw_patterns = config_data.get("episode_patterns", [default_episode_pattern])
+ for p in raw_patterns:
+ if isinstance(p, dict) and p.get("regex"):
+ episode_patterns.append(p)
+ elif isinstance(p, str):
+ episode_patterns.append({"regex": p})
+
+ # 如果没有有效的模式,使用默认模式
+ if not episode_patterns:
+ episode_patterns = [default_episode_pattern]
+
+ # 添加中文数字匹配模式
+ chinese_patterns = [
+ {"regex": r'第([一二三四五六七八九十百千万零两]+)集'},
+ {"regex": r'第([一二三四五六七八九十百千万零两]+)期'},
+ {"regex": r'第([一二三四五六七八九十百千万零两]+)话'},
+ {"regex": r'([一二三四五六七八九十百千万零两]+)集'},
+ {"regex": r'([一二三四五六七八九十百千万零两]+)期'},
+ {"regex": r'([一二三四五六七八九十百千万零两]+)话'}
+ ]
+ episode_patterns.extend(chinese_patterns)
+
+ # 处理每个文件
+ for file in filtered_files:
+ extension = os.path.splitext(file["file_name"])[1] if not file["dir"] else ""
+ # 从文件名中提取集号
+ episode_num = extract_episode_number(file["file_name"], episode_patterns=episode_patterns)
+
+ if episode_num is not None:
+ new_name = pattern.replace("[]", f"{episode_num:02d}") + extension
+ preview_results.append({
+ "original_name": file["file_name"],
+ "new_name": new_name,
+ "file_id": file["fid"]
+ })
+ else:
+ # 没有提取到集号,显示无法识别的提示
+ preview_results.append({
+ "original_name": file["file_name"],
+ "new_name": "× 无法识别剧集编号",
+ "file_id": file["fid"]
+ })
+
+ else:
+ # 正则命名模式
+ for file in filtered_files:
+ try:
+ # 应用正则表达式
+ if replace:
+ new_name = re.sub(pattern, replace, file["file_name"])
+ else:
+ # 如果没有提供替换表达式,则检查是否匹配
+ if re.search(pattern, file["file_name"]):
+ new_name = file["file_name"] # 匹配但不替换
+ else:
+ new_name = "" # 表示不匹配
+
+ preview_results.append({
+ "original_name": file["file_name"],
+ "new_name": new_name if new_name != file["file_name"] else "", # 如果没有改变,返回空表示不重命名
+ "file_id": file["fid"]
+ })
+ except Exception as e:
+ # 正则表达式错误
+ preview_results.append({
+ "original_name": file["file_name"],
+ "new_name": "", # 表示无法重命名
+ "file_id": file["fid"],
+ "error": str(e)
+ })
+
+ return jsonify({
+ "success": True,
+ "data": preview_results
+ })
+
+ except Exception as e:
+ logging.error(f">>> 预览重命名时出错: {str(e)}")
+ return jsonify({"success": False, "message": f"预览重命名时出错: {str(e)}"})
+
+
+# 批量重命名
+@app.route("/batch_rename", methods=["POST"])
+def batch_rename():
+ if not is_login():
+ return jsonify({"success": False, "message": "未登录"})
+
+ # 获取请求参数
+ data = request.json
+ files = data.get("files", [])
+
+ if not files:
+ return jsonify({"success": False, "message": "没有文件需要重命名"})
+
+ try:
+ # 初始化夸克网盘客户端
+ account = Quark(config_data["cookie"][0], 0)
+
+ # 批量重命名
+ success_count = 0
+ failed_files = []
+ save_path = data.get("save_path", "")
+ batch_time = int(time.time() * 1000) # 批次时间戳,确保同一批transfer_time一致
+ for file_item in files:
+ file_id = file_item.get("file_id")
+ new_name = file_item.get("new_name")
+ original_name = file_item.get("old_name") or ""
+ if file_id and new_name:
+ try:
+ result = account.rename(file_id, new_name)
+ if result.get("code") == 0:
+ success_count += 1
+ # 记录重命名
+ record_db.add_record(
+ task_name="rename",
+ original_name=original_name,
+ renamed_to=new_name,
+ file_size=0,
+ modify_date=int(time.time()),
+ file_id=file_id,
+ file_type="file",
+ save_path=save_path,
+ transfer_time=batch_time
+ )
+ else:
+ failed_files.append({
+ "file_id": file_id,
+ "new_name": new_name,
+ "error": result.get("message", "未知错误")
+ })
+ except Exception as e:
+ failed_files.append({
+ "file_id": file_id,
+ "new_name": new_name,
+ "error": str(e)
+ })
+
+ return jsonify({
+ "success": True,
+ "message": f"成功重命名 {success_count} 个文件,失败 {len(failed_files)} 个",
+ "success_count": success_count,
+ "failed_files": failed_files
+ })
+
+ except Exception as e:
+ logging.error(f">>> 批量重命名时出错: {str(e)}")
+ return jsonify({"success": False, "message": f"批量重命名时出错: {str(e)}"})
+
+
+# 撤销重命名接口
+@app.route("/undo_rename", methods=["POST"])
+def undo_rename():
+ if not is_login():
+ return jsonify({"success": False, "message": "未登录"})
+ data = request.json
+ save_path = data.get("save_path", "")
+ if not save_path:
+ return jsonify({"success": False, "message": "缺少目录参数"})
+ try:
+ account = Quark(config_data["cookie"][0], 0)
+ # 查询该目录下最近一次重命名(按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"]
+ if not rename_records:
+ return jsonify({"success": False, "message": "没有可撤销的重命名记录"})
+ # 找到最近一批(transfer_time最大值)
+ latest_time = max(r["transfer_time"] for r in rename_records)
+ latest_batch = [r for r in rename_records if r["transfer_time"] == latest_time]
+ success_count = 0
+ failed_files = []
+ deleted_ids = []
+ for rec in latest_batch:
+ file_id = rec["file_id"]
+ old_name = rec["original_name"]
+ try:
+ result = account.rename(file_id, old_name)
+ if result.get("code") == 0:
+ success_count += 1
+ deleted_ids.append(rec["id"])
+ else:
+ failed_files.append({
+ "file_id": file_id,
+ "old_name": old_name,
+ "error": result.get("message", "未知错误")
+ })
+ except Exception as e:
+ failed_files.append({
+ "file_id": file_id,
+ "old_name": old_name,
+ "error": str(e)
+ })
+ # 撤销成功的,直接删除对应rename记录
+ for rid in deleted_ids:
+ record_db.delete_record(rid)
+ return jsonify({
+ "success": True,
+ "message": f"成功撤销 {success_count} 个文件重命名,失败 {len(failed_files)} 个",
+ "success_count": success_count,
+ "failed_files": failed_files
+ })
+ except Exception as e:
+ logging.error(f">>> 撤销重命名时出错: {str(e)}")
+ return jsonify({"success": False, "message": f"撤销重命名时出错: {str(e)}"})
+
+
+@app.route("/api/has_rename_record")
+def has_rename_record():
+ save_path = request.args.get("save_path", "")
+ db = RecordDB()
+ records = db.get_records_by_save_path(save_path)
+ has_rename = any(r["task_name"] == "rename" for r in records)
+ return jsonify({"has_rename": has_rename})
+
+
if __name__ == "__main__":
init()
reload_tasks()
+ # 初始化全局db对象,确保所有接口可用
+ record_db = RecordDB()
app.run(debug=DEBUG, host="0.0.0.0", port=PORT)
diff --git a/app/sdk/db.py b/app/sdk/db.py
index 316de4e..3321f83 100644
--- a/app/sdk/db.py
+++ b/app/sdk/db.py
@@ -15,7 +15,7 @@ class RecordDB:
os.makedirs(os.path.dirname(self.db_path), exist_ok=True)
# 创建数据库连接
- self.conn = sqlite3.connect(self.db_path)
+ self.conn = sqlite3.connect(self.db_path, check_same_thread=False)
cursor = self.conn.cursor()
# 创建表,如果不存在
@@ -49,13 +49,14 @@ 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="", save_path=""):
+ duration="", resolution="", file_id="", file_type="", save_path="", transfer_time=None):
"""添加一条转存记录"""
cursor = self.conn.cursor()
+ now_ms = int(time.time() * 1000) if transfer_time is None else transfer_time
cursor.execute(
"INSERT INTO transfer_records (transfer_time, task_name, original_name, renamed_to, file_size, "
"duration, resolution, modify_date, file_id, file_type, save_path) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
- (int(time.time()), task_name, original_name, renamed_to, file_size,
+ (now_ms, task_name, original_name, renamed_to, file_size,
duration, resolution, modify_date, file_id, file_type, save_path)
)
self.conn.commit()
@@ -123,7 +124,7 @@ class RecordDB:
return 0
def get_records(self, page=1, page_size=20, sort_by="transfer_time", order="desc",
- task_name_filter="", keyword_filter=""):
+ task_name_filter="", keyword_filter="", exclude_task_names=None):
"""获取转存记录列表,支持分页、排序和筛选
Args:
@@ -133,6 +134,7 @@ class RecordDB:
order: 排序方向(asc/desc)
task_name_filter: 任务名称筛选条件(精确匹配)
keyword_filter: 关键字筛选条件(模糊匹配任务名)
+ exclude_task_names: 需要排除的任务名称列表
"""
cursor = self.conn.cursor()
offset = (page - 1) * page_size
@@ -159,6 +161,10 @@ class RecordDB:
params.append(f"%{keyword_filter}%")
params.append(f"%{keyword_filter}%")
+ if exclude_task_names:
+ where_clauses.append("task_name NOT IN ({})".format(",".join(["?" for _ in exclude_task_names])))
+ params.extend(exclude_task_names)
+
where_clause = " AND ".join(where_clauses)
where_sql = f"WHERE {where_clause}" if where_clause else ""
diff --git a/app/static/css/main.css b/app/static/css/main.css
index 8311f99..02f5743 100644
--- a/app/static/css/main.css
+++ b/app/static/css/main.css
@@ -94,6 +94,7 @@ body.login-page {
z-index: 9999;
width: auto;
max-width: 80%;
+ pointer-events: none;
}
.toast-custom {
@@ -1131,8 +1132,8 @@ textarea.form-control {
.expand-button {
position: absolute;
right: 5px;
- top: 10.5px; /* 恢复为固定位置 */
- transform: none; /* 移除垂直居中的转换 */
+ top: 11px; /* 固定位置,不再使用百分比定位 */
+ transform: none; /* 移除垂直居中转换 */
cursor: pointer;
opacity: 0;
transition: opacity 0.2s;
@@ -1152,12 +1153,11 @@ textarea.form-control {
.expanded-text {
white-space: normal;
- padding: 0 0; /* 展开时的上下内边距 */
word-break: break-all;
display: block; /* 确保展开的内容是块级元素 */
- line-height: 1.5; /* 设置合理的行高 */
- margin-top: 4px; /* 与上方内容保持一定间距 */
- margin-bottom: 4px; /* 添加底部边距,与表格底部分割线保持一致的距离 */
+ line-height: 24px !important; /* 与表头保持一致的行高 */
+ margin-top: 0; /* 移除顶部边距 */
+ margin-bottom: 0; /* 移除底部边距 */
position: relative; /* 确保定位准确 */
padding-right: 25px; /* 为展开按钮预留空间 */
}
@@ -1174,25 +1174,18 @@ textarea.form-control {
overflow-x: auto;
}
-.table th {
- white-space: nowrap;
- background-color: #f8f9fa;
- position: sticky;
- top: 0;
- z-index: 10;
-}
-
/* 表头样式调整 */
-.table thead th {
+.table thead th,
+table.table thead th {
vertical-align: middle;
- border-bottom: 1px solid var(--border-color); /* 修改底部边框为1px */
- border-top: 1px solid var(--border-color); /* 添加上边框线 */
- padding-top: 8.5px; /* 增加上内边距 */
- padding-bottom: 8.5px; /* 增加下内边距 */
- padding-left: 9px !important; /* 表头左内边距,与按钮一致 */
- padding-right: 9px !important; /* 表头右内边距,与按钮一致 */
- background-color: var(--button-gray-background-color); /* 表头背景色 */
- color: var(--dark-text-color); /* 表头文字颜色 */
+ border-bottom: 1px solid var(--border-color) !important; /* 修改底部边框为1px */
+ border-top: 1px solid var(--border-color) !important; /* 添加上边框线 */
+ padding: 8px 9px !important; /* 统一表头内边距 */
+ background-color: var(--button-gray-background-color) !important; /* 表头背景色 */
+ color: var(--dark-text-color) !important; /* 表头文字颜色 */
+ height: 40px !important; /* 确保表头高度为40px */
+ line-height: 24px !important; /* 设置行高以确保文字垂直居中 */
+ box-sizing: border-box !important; /* 确保边框包含在总高度内 */
}
/* 表头悬停样式 */
@@ -1202,18 +1195,17 @@ textarea.form-control {
/* 表格单元格样式 */
.table td {
- vertical-align: top; /* 改为顶部对齐,避免内容居中问题 */
- height: auto; /* 根据内容自动调整高度 */
+ vertical-align: middle !important; /* 统一使用垂直居中对齐 */
+ height: 40px !important; /* 与表头保持一致的高度 */
max-width: 300px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
- padding-top: 8px; /* 单元格上内边距 */
- padding-bottom: 8px; /* 单元格下内边距 */
- padding-left: 9px !important; /* 单元格左内边距,与按钮一致 */
- padding-right: 9px !important; /* 单元格右内边距,与按钮一致 */
+ padding: 8px 9px !important; /* 统一单元格内边距 */
border-bottom: 1px solid var(--border-color); /* 单元格分割线颜色 */
- transform: translateY(0.5px); /* 文本下移0.5px,不影响元素实际高度 */
+ border-top: none !important; /* 确保没有上边框 */
+ box-sizing: border-box !important; /* 确保边框包含在总高度内 */
+ line-height: 24px !important; /* 与表头保持一致的行高 */
}
/* 表格行悬停样式 */
@@ -1227,23 +1219,22 @@ textarea.form-control {
word-break: break-word;
}
-/* 新增:设置表格单元格中的position-relative样式,以便正确定位内容 */
+/* 设置表格单元格中的position-relative样式,以便正确定位内容 */
.table td.position-relative {
position: relative; /* 确保相对定位生效 */
- padding-top: 4px; /* 减少顶部内边距 */
- padding-bottom: 4px; /* 减少底部内边距 */
+ padding: 8px 9px !important; /* 保持与其他单元格一致的内边距 */
+ vertical-align: top !important; /* 确保内容顶部对齐 */
}
-/* 新增:调整text-truncate在表格单元格中的布局 */
+/* 调整text-truncate在表格单元格中的布局 */
.table td .text-truncate {
display: block; /* 使其成为块级元素 */
max-width: 100%; /* 确保不超出单元格宽度 */
padding-right: 25px; /* 为展开按钮留出空间 */
- padding-top: 4px; /* 与单元格顶部保持一定距离 */
- padding-bottom: 4px; /* 与单元格底部保持一定距离 */
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
+ line-height: 24px !important; /* 与表头保持一致的行高 */
}
/* --------------- 模态框样式 --------------- */
@@ -1606,7 +1597,7 @@ button.close:focus,
top: 0;
z-index: 5;
vertical-align: middle; /* 添加:垂直居中对齐 */
- height: auto; /* 添加:自动高度,确保与内容一致 */
+ height: 40px !important; /* 添加:自动高度,确保与内容一致 */
}
/* 模态框表格列宽设置 - 基于内容类型 */
@@ -1689,15 +1680,6 @@ button.close:focus,
cursor: pointer;
}
-/* 弹窗内文件夹和文件图标样式 */
-#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;
@@ -2150,6 +2132,10 @@ div.jsoneditor-tree button.jsoneditor-button:focus {
font-size: 1rem;
}
+.sidebar .nav-link .bi-archive {
+ font-size: 1rem;
+}
+
.sidebar .nav-link .bi-power {
font-size: 1.27rem;
}
@@ -3345,7 +3331,7 @@ div[id^="collapse_"][id*="plugin"] .input-group {
#fileSelectModal .badge-info {
background-color: var(--focus-border-color) !important;
color: white !important;
- font-size: 0.84rem !important;
+ font-size: 0.83rem !important;
padding: 6px 3px !important;
font-weight: normal !important;
border-radius: 4px !important;
@@ -3386,7 +3372,7 @@ div[id^="collapse_"][id*="plugin"] .input-group {
/* 多行表达式间距 - 针对预览区域显示的多行表达式 */
#fileSelectModal .mb-3[v-if="fileSelect.previewRegex"] > div > span.badge-info + span.badge-info {
- margin-top: 1px !important; /* 减少多行表达式之间的间距 */
+ margin-top: 5px !important; /* 减少多行表达式之间的间距 */
display: inline-block;
}
@@ -3602,10 +3588,22 @@ input::-moz-list-button {
padding-right: 16px !important; /* 强制设置右边距为16px */
box-sizing: border-box;
}
-
+
#fileSelectModal[data-modal-type="preview"] .table {
width: 460px;
}
+
+ /* 针对文件整理页面命名预览模式 - 2列表格 */
+ #fileSelectModal[data-modal-type="preview-filemanager"] .breadcrumb {
+ min-width: 460px; /* 2列表格总宽度: 230px + 230px */
+ margin-right: 0;
+ padding-right: 16px !important; /* 强制设置右边距为16px */
+ box-sizing: border-box;
+ }
+
+ #fileSelectModal[data-modal-type="preview-filemanager"] .table {
+ width: 460px;
+ }
/* 确保面包屑导航内容不被截断 */
#fileSelectModal .breadcrumb-item {
@@ -3688,7 +3686,7 @@ input::-moz-list-button {
#fileSelectModal .expand-button {
position: absolute;
right: 5px;
- top: 7.5px; /* 固定高度,不再使用百分比定位 */
+ top: 7.5px; /* 固定位置 */
transform: none; /* 移除垂直居中转换 */
cursor: pointer;
opacity: 0;
@@ -3717,6 +3715,7 @@ input::-moz-list-button {
/* 确保模态框中的表格单元格可以正确显示展开按钮 */
#fileSelectModal .table td.position-relative {
position: relative;
+ vertical-align: top !important; /* 确保内容顶部对齐 */
}
/* 当表格行被点击时,防止展开按钮的点击事件冒泡 */
@@ -3728,6 +3727,8 @@ input::-moz-list-button {
#fileSelectModal .table td [style*="white-space: normal"] {
display: block;
width: 100%;
+ margin-top: 0; /* 移除顶部边距 */
+ margin-bottom: 7px;
}
/* 确保表格行内容保持顶部对齐 */
@@ -3799,24 +3800,8 @@ input.no-spinner {
/* 文件大小值的文本位置调整 */
.file-size-cell .file-size-value {
- transform: translateY(-1px); /* 文本下移 */
display: inline-block; /* 确保transform生效 */
-}
-
-/* 转存记录删除按钮样式 */
-.delete-record-btn {
- color: #dc3545;
- cursor: pointer;
- display: inline-flex;
- align-items: center;
- justify-content: center;
- width: 24px;
- height: 24px;
- border-radius: 4px;
- transition: background-color 0.2s ease;
- visibility: hidden; /* 默认隐藏 */
- position: relative; /* 添加相对定位 */
- top: -0.5px; /* 上移 */
+ transform: translateY(0px);
}
/* 删除按钮图标大小 */
@@ -3845,6 +3830,7 @@ table.selectable-records {
table.selectable-records tbody tr {
cursor: pointer;
+ transition: none;
}
/* 修改表格行悬停样式,使用变量保持一致性 */
@@ -3878,6 +3864,10 @@ tr.selected-record .file-size-cell .delete-record-btn,
height: 100%;
margin-left: 0; /* 确保没有左边距 */
padding-left: 0; /* 确保没有左内边距 */
+ left: 9px; /* 与大小值保持相同的左边距 */
+ position: absolute; /* 使用绝对定位 */
+ top: 8px; /* 固定位置 */
+ transform: none; /* 移除垂直居中转换 */
}
/* 当鼠标悬停在展开按钮或删除按钮上时,不改变按钮的背景色 */
@@ -3898,24 +3888,37 @@ table.selectable-records .expand-button:hover {
/* 特别调整红色"×"符号的位置 */
#fileSelectModal .table td.col-rename.text-danger div:not(.expand-button) {
position: relative;
- top: -1px; /* 将"×"标记上移1px */
+ top: 2px !important; /* 将"×"标记上移1px */
+}
+
+#fileSelectModal[data-modal-type="preview"] .table td.col-rename > * {
+ position: relative;
+ top: 3px !important; /* 原来是3.5px,上移0.5px */
+}
+
+/* 文件整理页面命名预览模式下的重命名列通用样式 */
+#fileSelectModal[data-modal-type="preview-filemanager"] .table td.col-rename > * {
+ position: relative;
+ top: 3px !important; /* 与任务配置页面保持一致 */
}
/* 文件名列已经通过图标微调过,保持原样或细微调整 */
#fileSelectModal .bi-folder-fill {
color: #ffc107;
- font-size: 0.9rem;
- margin-right: 5px;
+ font-size: 0.95rem;
+ margin-right: 4.5px;
position: relative;
- top: 1px; /* 负值向上移动,正值向下移动 */
+ top: 0.5px !important; /* 负值向上移动,正值向下移动 */
+ left: -0.55px;
}
#fileSelectModal .bi-file-earmark {
color: var(--dark-text-color);
- font-size: 0.9rem;
- margin-right: 5px;
+ font-size: 0.95rem;
+ margin-right: 4.5px;
position: relative;
- top: 0px; /* 负值向上移动,正值向下移动 */
+ top: 0.5px !important; /* 负值向上移动,正值向下移动 */
+ left: -0.4px;
}
/* 添加选中文件的样式 */
@@ -4129,3 +4132,943 @@ select.task-filter-select,
.task-name-hover:hover {
color: var(--focus-border-color) !important;
}
+
+/* 文件整理页面样式 */
+.file-manager-card {
+ display: none; /* 隐藏卡片 */
+}
+
+.file-manager-card .card-body {
+ padding: 0; /* 移除内边距 */
+}
+
+.file-manager-input {
+ flex: 1;
+}
+
+/* 文件整理页面面包屑导航样式 */
+.file-manager-breadcrumb {
+ margin-top: 20px;
+}
+
+.file-manager-breadcrumb .breadcrumb-item {
+ color: var(--dark-text-color);
+ padding: 0;
+ position: relative; /* 添加相对定位 */
+}
+
+.file-manager-breadcrumb .breadcrumb-item a {
+ color: var(--dark-text-color);
+ text-decoration: none;
+}
+
+.file-manager-breadcrumb .breadcrumb-item a:hover {
+ color: var(--focus-border-color);
+}
+
+/* 隐藏Bootstrap默认的面包屑分隔符 */
+.file-manager-breadcrumb .breadcrumb-item + .breadcrumb-item::before {
+ color: var(--dark-text-color);
+ content: "/"; /* 使用斜杠作为分隔符 */
+ position: relative;
+ display: inline-block;
+ vertical-align: middle; /* 垂直居中 */
+ padding: 0 8px; /* 在斜杠两侧添加间距 */
+ top: -1px; /* 将分隔符上移1px */
+}
+
+.file-actions {
+ white-space: nowrap;
+}
+
+.selectable-files tr.selected-file {
+ background-color: rgba(var(--primary-rgb), 0.1);
+}
+
+.batch-rename-btn {
+ margin-left: 8px;
+}
+
+/* 文件表格中的展开按钮 */
+.expand-button {
+ position: absolute;
+ right: 5px;
+ top: 11px; /* 固定位置,与其他表格保持一致 */
+ transform: none; /* 移除垂直居中转换 */
+ cursor: pointer;
+ opacity: 0;
+ transition: opacity 0.2s;
+ background: #fff;
+ border-radius: 50%;
+ width: 18px;
+ height: 18px;
+ text-align: center;
+ line-height: 18px;
+ box-shadow: 0 0 3px rgba(0, 0, 0, 0.2);
+ z-index: 2;
+}
+
+.expand-button:hover {
+ opacity: 1;
+}
+
+/* 批量重命名模态框样式 */
+#batchRenameModal .modal-dialog {
+ max-width: 80%;
+}
+
+#batchRenameModal .table {
+ margin-bottom: 0;
+}
+
+#batchRenameModal .badge {
+ font-size: 90%;
+ font-weight: 500;
+ word-break: break-all;
+}
+
+#batchRenameModal .alert {
+ margin-bottom: 15px;
+}
+
+#batchRenameModal .text-danger {
+ background-color: rgba(var(--danger-rgb), 0.05);
+}
+
+/* 响应式调整 */
+@media (max-width: 768px) {
+ .file-manager-rule-bar {
+ flex-direction: column;
+ }
+
+ .file-manager-rule-bar .input-group {
+ margin-bottom: 10px;
+ }
+
+ .batch-rename-btn {
+ margin-left: 0;
+ margin-top: 10px;
+ }
+
+ #batchRenameModal .modal-dialog {
+ max-width: 95%;
+ }
+}
+
+/* 文件大小单元格样式 */
+.file-size-cell {
+ position: relative;
+}
+
+.file-size-cell .file-size-value {
+ display: inline-block;
+}
+
+.delete-record-btn .bi-trash3 {
+ font-size: 1rem;
+}
+
+/* 当行被选中或鼠标悬停时显示删除按钮 */
+tr:hover .delete-record-btn,
+tr.selected-file .delete-record-btn {
+ display: inline-block;
+}
+
+/* 表格标题行不显示删除按钮 */
+table th .delete-record-btn {
+ display: none !important;
+}
+
+/* 可选择的表格样式 */
+table.selectable-files {
+ border-collapse: separate;
+ border-spacing: 0;
+}
+
+table.selectable-files tbody tr {
+ transition: none;
+}
+
+table.selectable-files tbody tr:hover {
+ background-color: rgba(var(--primary-rgb), 0.05);
+}
+
+/* 选中行时展开按钮的样式 */
+tr.selected-file .expand-button {
+ background-color: var(--button-gray-background-color);
+}
+
+/* 展开按钮悬停样式 */
+table.selectable-files .expand-button:hover,
+tr.selected-file .expand-button:hover {
+ background-color: #fff;
+ opacity: 1;
+}
+
+/* 选中行时文件大小和删除按钮的样式 */
+tr.selected-file .file-size-cell .file-size-value,
+tr.selected-file .file-size-cell .delete-record-btn {
+ position: relative;
+ z-index: 1;
+}
+
+/* 文件整理页面表格样式 */
+.selectable-files {
+ border-collapse: collapse !important;
+}
+
+.selectable-files td,
+.selectable-files th {
+ border: none !important;
+ border-top: 1px solid var(--border-color) !important; /* 添加顶部边框线 */
+ border-bottom: 1px solid var(--border-color) !important; /* 添加底部分割线 */
+}
+
+.selectable-files tbody tr {
+ cursor: pointer;
+ transition: none;
+}
+
+.selectable-files tbody tr:hover {
+ background-color: var(--button-gray-background-color) !important;
+}
+
+/* 删除按钮样式调整 */
+.selectable-files .delete-record-btn {
+ color: #dc3545;
+ cursor: pointer;
+ display: none;
+ align-items: center;
+ justify-content: flex-start; /* 居左对齐 */
+ width: 24px;
+ height: 24px;
+ border-radius: 4px;
+ transition: background-color 0.2s ease;
+ position: absolute;
+ right: auto; /* 移除右对齐 */
+ left: 9px; /* 与大小值保持相同的左边距 */
+ top: 50%;
+ transform: translateY(-50%);
+}
+
+.selectable-files tr:hover .delete-record-btn,
+.selectable-files tr.selected-file .delete-record-btn {
+ display: inline-flex;
+}
+
+.selectable-files tr:hover .file-size-value,
+.selectable-files tr.selected-file .file-size-value {
+ visibility: hidden;
+}
+
+/* 确保选中行的样式正确 */
+.selectable-files tr.selected-file {
+ background-color: var(--button-gray-background-color) !important;
+}
+
+/* 使面包屑导航样式与表头一致 */
+.file-manager-breadcrumb .breadcrumb {
+ background-color: var(--button-gray-background-color) !important;
+ border-radius: 0px;
+ font-size: 0.95rem;
+ padding: 0 9px !important;
+ margin-bottom: 8px;
+ border-top: 1px solid var(--border-color) !important;
+ border-bottom: 1px solid var(--border-color) !important;
+ display: flex;
+ align-items: center;
+ height: 42px !important; /* 确保面包屑导航内容区域高度为42px(包含上下边框) */
+ line-height: 23px !important; /* 设置行高以确保文字垂直居中 */
+ box-sizing: border-box !important; /* 确保边框包含在总高度内 */
+}
+
+.file-manager-breadcrumb .breadcrumb-item {
+ color: var(--dark-text-color);
+ padding: 0;
+ position: relative; /* 添加相对定位 */
+}
+
+.file-manager-breadcrumb .breadcrumb-item a {
+ color: var(--dark-text-color);
+ text-decoration: none;
+}
+
+.file-manager-breadcrumb .breadcrumb-item a:hover {
+ color: var(--focus-border-color);
+}
+
+/* 让"全部文件"链接悬停时文字变为蓝色 */
+.file-manager-breadcrumb .breadcrumb-item.cursor-pointer:hover {
+ background-color: transparent; /* 移除背景色 */
+ color: var(--focus-border-color);
+}
+
+.file-manager-breadcrumb .breadcrumb-item.cursor-pointer:hover {
+ color: var(--focus-border-color);
+}
+
+/* 文件整理页面样式 */
+.file-manager-input {
+ flex: 1;
+}
+
+/* 文件整理页面面包屑导航样式 */
+.file-manager-breadcrumb {
+ margin-top: 20px;
+}
+
+/* 文件整理规则栏样式 */
+.file-manager-rule-bar {
+ width: 100%;
+}
+
+/* 禁止在表格中选择文本,以便更好地支持点击选择 */
+table.selectable-files {
+ user-select: none;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+}
+
+/* 确保展开按钮在选中状态下仍然可见 */
+tr.selected-file .expand-button {
+ z-index: 2;
+}
+
+/* 当鼠标悬停在展开按钮或删除按钮上时,不改变按钮的背景色 */
+table.selectable-files .expand-button:hover,
+table.selectable-files .delete-record-btn:hover {
+ background-color: transparent !important;
+}
+
+/* 选中行或鼠标悬停行的大小列样式 */
+tr.selected-file .file-size-cell .file-size-value,
+.selectable-files tbody tr:hover .file-size-cell .file-size-value {
+ display: none; /* 隐藏文件大小信息 */
+}
+
+tr.selected-file .file-size-cell .delete-record-btn,
+.selectable-files tbody tr:hover .file-size-cell .delete-record-btn {
+ display: flex;
+ justify-content: flex-start; /* 居左对齐 */
+ align-items: center;
+ width: auto;
+ height: 100%;
+ margin-left: 0; /* 确保没有左边距 */
+ padding-left: 0; /* 确保没有左内边距 */
+ left: 9px; /* 与大小值保持相同的左边距 */
+ position: absolute; /* 使用绝对定位 */
+ top: 50%; /* 垂直居中 */
+ transform: translateY(-50%); /* 确保垂直居中 */
+}
+
+/* 当鼠标悬停在展开按钮或删除按钮上时,不改变按钮的背景色 */
+table.selectable-files .expand-button:hover {
+ background-color: #fff !important; /* 保持展开按钮原有的白色背景 */
+}
+
+/* 文件整理页面表头样式,与转存记录页面保持一致 */
+.selectable-files th,
+table.selectable-files th {
+ vertical-align: middle;
+ border-bottom: 1px solid var(--border-color) !important; /* 修改底部边框为1px */
+ border-top: 1px solid var(--border-color) !important; /* 添加上边框线 */
+ padding: 8px 9px !important; /* 统一表头内边距 */
+ background-color: var(--button-gray-background-color) !important; /* 表头背景色 */
+ color: var(--dark-text-color) !important; /* 表头文字颜色 */
+ height: 40px !important; /* 确保表头高度为40px */
+ line-height: 24px !important; /* 设置行高以确保文字垂直居中 */
+ box-sizing: border-box !important; /* 确保边框包含在总高度内 */
+}
+
+/* 文件整理页面表格单元格样式,与转存记录页面保持一致 */
+.selectable-files td,
+table.selectable-files td {
+ vertical-align: middle !important; /* 统一使用垂直居中对齐 */
+ height: 40px !important; /* 与表头保持一致的高度 */
+ max-width: 300px;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ padding: 8px 9px !important; /* 统一单元格内边距 */
+ border-bottom: 1px solid var(--border-color); /* 单元格分割线颜色 */
+ border-top: none !important; /* 确保没有上边框 */
+ box-sizing: border-box !important; /* 确保边框计入总高度 */
+ line-height: 24px !important; /* 与表头保持一致的行高 */
+}
+
+/* 确保文件大小列的垂直对齐保持一致 */
+.table .file-size-cell,
+.selectable-files .file-size-cell,
+table.selectable-files .file-size-cell {
+ vertical-align: middle !important;
+}
+
+/* 表头悬停样式 */
+.table thead th.cursor-pointer:hover {
+ background-color: #f7f7fa; /* 表头悬停背景色 */
+}
+
+/* 确保所有表格的表头悬停样式一致 */
+.table th.cursor-pointer:hover,
+.selectable-files th.cursor-pointer:hover,
+table.selectable-files th.cursor-pointer:hover,
+table.selectable-records th.cursor-pointer:hover {
+ background-color: #f7f7fa !important; /* 表头悬停背景色 */
+ cursor: pointer;
+}
+
+/* 添加表头排序列的鼠标指针样式 */
+.table th.sortable,
+.selectable-files th.sortable,
+table.selectable-files th.sortable,
+table.selectable-records th.sortable {
+ cursor: pointer;
+}
+
+/* 确保展开后其他列(如转存日期、大小、修改日期)的内容保持在原始位置 */
+.table tr td {
+ vertical-align: top !important; /* 确保所有单元格内容顶部对齐 */
+}
+
+/* 确保文件整理页面和模态框中的表格也应用相同的规则 */
+#fileSelectModal .table tr td,
+.selectable-files tr td,
+table.selectable-files tr td,
+table.selectable-records tr td {
+ vertical-align: top !important; /* 确保所有单元格内容顶部对齐 */
+}
+
+/* 确保展开后的行中所有单元格内容保持原位 */
+.table tr:has(.expanded-text) td,
+#fileSelectModal .table tr:has([style*="white-space: normal"]) td,
+.selectable-files tr:has([style*="white-space: normal"]) td {
+ vertical-align: top !important; /* 确保展开后所有单元格内容顶部对齐 */
+}
+
+/* 确保删除按钮在展开行中的位置保持不变 */
+tr:has(.expanded-text) .delete-record-btn,
+#fileSelectModal .table tr:has([style*="white-space: normal"]) .delete-record-btn,
+.selectable-files tr:has([style*="white-space: normal"]) .delete-record-btn {
+ top: 8px !important; /* 强制固定位置 */
+ transform: none !important; /* 确保不使用任何转换 */
+}
+
+/* 文件大小列中展开行的删除按钮特殊处理 */
+tr:has(.expanded-text) .file-size-cell .delete-record-btn,
+.selectable-records tbody tr:has(.expanded-text) .file-size-cell .delete-record-btn,
+#fileSelectModal .table tr:has([style*="white-space: normal"]) .file-size-cell .delete-record-btn,
+.selectable-files tr:has([style*="white-space: normal"]) .file-size-cell .delete-record-btn {
+ top: 8px !important; /* 强制固定位置 */
+ transform: none !important; /* 确保不使用任何转换 */
+ display: flex !important; /* 确保显示 */
+ left: 9px !important; /* 确保左边距固定 */
+}
+
+/* 修复删除按钮位置问题 - 使用更强制的方法 */
+.delete-record-btn {
+ color: #dc3545;
+ cursor: pointer;
+ display: inline-flex;
+ align-items: center;
+ justify-content: flex-start;
+ width: 24px;
+ height: 24px;
+ border-radius: 4px;
+ transition: background-color 0.2s ease;
+ visibility: hidden;
+ position: absolute !important; /* 强制绝对定位 */
+ top: 8px !important; /* 强制固定位置 */
+ transform: none !important; /* 强制禁用任何转换 */
+ left: 0;
+}
+
+/* 删除按钮图标大小 */
+.delete-record-btn .bi-trash3 {
+ font-size: 1rem;
+}
+
+/* 选中行或鼠标悬停行时显示删除按钮 */
+tr.selected-record .delete-record-btn,
+.selectable-records tbody tr:hover .delete-record-btn {
+ visibility: visible;
+}
+
+/* 表头中的删除按钮仅在有选中行时显示 */
+table th .delete-record-btn {
+ visibility: hidden;
+}
+
+/* 选中行或鼠标悬停行的大小列样式 */
+tr.selected-record .file-size-cell .file-size-value,
+.selectable-records tbody tr:hover .file-size-cell .file-size-value {
+ display: none; /* 隐藏文件大小信息 */
+}
+
+/* 文件大小列中的删除按钮特殊处理 - 完全重写这部分规则 */
+tr .file-size-cell .delete-record-btn,
+tr.selected-record .file-size-cell .delete-record-btn,
+.selectable-records tbody tr .file-size-cell .delete-record-btn,
+.selectable-records tbody tr:hover .file-size-cell .delete-record-btn {
+ display: flex;
+ justify-content: flex-start;
+ align-items: center;
+ width: auto;
+ height: 24px;
+ margin-left: 0;
+ padding-left: 0;
+ left: 9px !important; /* 强制固定左边距 */
+ position: absolute !important; /* 强制绝对定位 */
+ top: 8px !important; /* 强制固定位置 */
+ transform: none !important; /* 强制禁用任何转换 */
+}
+
+/* 确保在展开行中删除按钮位置保持不变 - 对所有可能的情况进行覆盖 */
+tr:has(.expanded-text) .delete-record-btn,
+tr.selected-record:has(.expanded-text) .delete-record-btn,
+.selectable-records tbody tr:has(.expanded-text) .delete-record-btn,
+.selectable-records tbody tr:hover:has(.expanded-text) .delete-record-btn,
+#fileSelectModal .table tr:has([style*="white-space: normal"]) .delete-record-btn,
+.selectable-files tr:has([style*="white-space: normal"]) .delete-record-btn {
+ top: 8px !important; /* 强制固定位置 */
+ transform: none !important; /* 强制禁用任何转换 */
+}
+
+/* 文件大小列中展开行的删除按钮特殊处理 - 对所有可能的情况进行覆盖 */
+tr:has(.expanded-text) .file-size-cell .delete-record-btn,
+tr.selected-record:has(.expanded-text) .file-size-cell .delete-record-btn,
+.selectable-records tbody tr:has(.expanded-text) .file-size-cell .delete-record-btn,
+.selectable-records tbody tr:hover:has(.expanded-text) .file-size-cell .delete-record-btn,
+#fileSelectModal .table tr:has([style*="white-space: normal"]) .file-size-cell .delete-record-btn,
+.selectable-files tr:has([style*="white-space: normal"]) .file-size-cell .delete-record-btn {
+ top: 8px !important; /* 强制固定位置 */
+ transform: none !important; /* 强制禁用任何转换 */
+ display: flex !important; /* 确保显示 */
+ left: 9px !important; /* 确保左边距固定 */
+}
+
+/* 文件整理页面表格行悬停样式 */
+.selectable-files tbody tr:hover {
+ background-color: var(--button-gray-background-color);
+}
+
+/* --------------- 文件整理页面样式 --------------- */
+
+/* 删除按钮样式调整 */
+.selectable-files .delete-record-btn {
+ color: #dc3545;
+ cursor: pointer;
+ display: none;
+ align-items: center;
+ justify-content: flex-start; /* 居左对齐 */
+ width: 24px;
+ height: 24px;
+ border-radius: 4px;
+ transition: background-color 0.2s ease;
+ position: absolute;
+ right: auto; /* 移除右对齐 */
+ left: 9px; /* 与大小值保持相同的左边距 */
+ top: 50%;
+ transform: translateY(-50%);
+}
+
+/* 修复:确保在悬停和选中状态下删除按钮显示 */
+.selectable-files tr:hover .file-size-cell .delete-record-btn,
+.selectable-files tr.selected-file .file-size-cell .delete-record-btn {
+ display: inline-flex !important;
+ visibility: visible !important;
+}
+
+/* 修复:确保在悬停和选中状态下文件大小值隐藏 */
+.selectable-files tr:hover .file-size-cell .file-size-value,
+.selectable-files tr.selected-file .file-size-cell .file-size-value {
+ display: none !important;
+ visibility: hidden !important;
+}
+
+/* 修复:确保在悬停和选中状态下删除按钮显示 */
+.selectable-files tbody tr:hover .file-size-cell .delete-record-btn,
+.selectable-files tr.selected-file .file-size-cell .delete-record-btn {
+ display: flex !important;
+ visibility: visible !important;
+ position: absolute !important;
+ top: 8px !important;
+ left: 9px !important;
+ transform: none !important;
+ height: 24px !important;
+ width: auto !important;
+ align-items: center !important;
+ justify-content: flex-start !important;
+ z-index: 5 !important; /* 确保删除按钮在其他元素之上 */
+}
+
+/* 修复文件大小列中的删除按钮特殊处理 */
+.selectable-files .file-size-cell .delete-record-btn {
+ position: absolute !important;
+ left: 9px !important;
+ top: 8px !important;
+ transform: none !important;
+ display: none; /* 默认隐藏 */
+ visibility: hidden; /* 默认隐藏 */
+}
+
+/* 修复:确保在悬停和选中状态下文件大小值隐藏 */
+.selectable-files tbody tr:hover .file-size-cell .file-size-value,
+.selectable-files tr.selected-file .file-size-cell .file-size-value {
+ display: none !important;
+ visibility: hidden !important;
+}
+
+/* 确保文件整理页面的删除按钮在悬停和选中状态下始终可见 - 最高优先级 */
+body .selectable-files tbody tr:hover .file-size-cell .delete-record-btn,
+body .selectable-files tr.selected-file .file-size-cell .delete-record-btn {
+ display: flex !important;
+ visibility: visible !important;
+ position: absolute !important;
+ top: 8px !important;
+ left: 9px !important;
+ transform: none !important;
+ height: 24px !important;
+ width: auto !important;
+ align-items: center !important;
+ justify-content: flex-start !important;
+ z-index: 5 !important;
+ opacity: 1 !important;
+}
+
+/* 文件整理页面的文件名单元格样式 */
+.selectable-files td .text-truncate {
+ display: block;
+ max-width: 100%;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ padding-right: 25px; /* 为展开按钮预留空间 */
+ line-height: inherit; /* 使用继承的行高 */
+}
+
+/* 文件整理页面展开后的文本样式 */
+.selectable-files td div:not(.text-truncate):not(.expand-button):not(.d-flex) {
+ white-space: normal;
+ word-break: break-all;
+ display: block;
+ line-height: inherit; /* 使用继承的行高 */
+ margin-top: 0;
+ margin-bottom: -1px;
+ position: relative;
+ padding-right: 25px;
+ max-width: 100%; /* 确保不超出单元格宽度 */
+ top: -1px;
+}
+
+/* 展开按钮悬停状态 */
+.selectable-files .expand-button:hover {
+ background-color: #fff !important; /* 保持白色背景 */
+ opacity: 1 !important;
+}
+
+/* 展开按钮样式 */
+.selectable-files .expand-button {
+ position: absolute;
+ right: 5px;
+ top: 11px; /* 固定位置,不使用垂直居中 */
+ transform: none; /* 移除垂直居中转换 */
+ cursor: pointer;
+ opacity: 0;
+ transition: opacity 0.2s;
+ background: #fff;
+ border-radius: 50%;
+ width: 18px;
+ height: 18px;
+ text-align: center;
+ line-height: 18px;
+ box-shadow: 0 0 3px rgba(0, 0, 0, 0.2);
+ z-index: 2;
+ pointer-events: auto; /* 确保点击事件不被阻止 */
+}
+
+/* 展开按钮悬停时可见 */
+.selectable-files .position-relative:hover .expand-button {
+ opacity: 1;
+}
+
+/* 展开按钮中的图标样式 */
+.selectable-files .expand-button .bi {
+ font-size: 0.7em;
+ vertical-align: middle;
+ position: relative;
+ top: -1px;
+}
+
+/* 确保单元格垂直居中 */
+.selectable-files td {
+ vertical-align: middle !important;
+}
+
+/* 确保图标与文本垂直对齐 */
+.selectable-files .d-flex.align-items-center {
+ align-items: center !important;
+}
+
+/* 确保图标固定大小不变形 */
+.selectable-files .d-flex.align-items-center i.bi {
+ flex-shrink: 0;
+}
+
+/* 确保图标与文本垂直对齐 - 使用固定高度 */
+.selectable-files .d-flex.align-items-center {
+ align-items: flex-start !important; /* 改为顶部对齐,使用固定高度 */
+}
+
+/* 确保图标固定大小不变形,使用固定位置 */
+.selectable-files .d-flex.align-items-center i.bi {
+ flex-shrink: 0;
+ position: relative;
+ top: 0px; /* 固定图标位置,向下移动4px */
+}
+
+/* 修复文件整理页面展开后文件大小列的垂直对齐问题 */
+/* 确保展开后的行中所有单元格都使用顶部对齐 */
+.selectable-files tr:has([style*="white-space: normal"]) td,
+.selectable-files tr:has(div:not(.text-truncate):not(.expand-button):not(.d-flex)) td {
+ vertical-align: top !important; /* 强制展开后所有单元格顶部对齐 */
+}
+
+/* 特别处理文件大小列,确保展开后也使用顶部对齐 */
+.selectable-files tr:has([style*="white-space: normal"]) .file-size-cell,
+.selectable-files tr:has(div:not(.text-truncate):not(.expand-button):not(.d-flex)) .file-size-cell {
+ vertical-align: top !important; /* 强制文件大小列顶部对齐 */
+}
+
+/* 确保文件大小列中的删除按钮在展开状态下位置正确 */
+.selectable-files tr:has([style*="white-space: normal"]) .file-size-cell .delete-record-btn,
+.selectable-files tr:has(div:not(.text-truncate):not(.expand-button):not(.d-flex)) .file-size-cell .delete-record-btn {
+ top: 8px !important; /* 强制固定位置 */
+ transform: none !important; /* 强制禁用任何转换 */
+ display: flex !important; /* 确保显示 */
+ left: 9px !important; /* 确保左边距固定 */
+ position: absolute !important; /* 强制绝对定位 */
+}
+
+/* 确保选中状态下文件整理页面的展开按钮也保持白色背景 */
+.selectable-files .expand-button {
+ background-color: #fff !important;
+}
+
+/* 文件整理页面重命名配置框输入框样式 - 去除圆角 */
+.file-manager-rule-bar .file-manager-input {
+ border-radius: 0 !important; /* 去除圆角 */
+}
+
+/* 文件整理页面重命名配置框输入框相邻边框重叠样式 */
+.file-manager-rule-bar .file-manager-input:not(:first-child) {
+ margin-left: -1px; /* 向左移动1px,使边框重叠 */
+}
+
+/* 确保输入框在输入组中的边框重叠 */
+.file-manager-rule-bar .input-group .file-manager-input:not(:first-child) {
+ border-left: 1px solid var(--border-color) !important; /* 确保左边框显示 */
+ margin-left: -1px; /* 向左移动1px,使边框重叠 */
+}
+
+/* 文件整理页面正则命名按钮样式 - 保持左边圆角,去除右边圆角 */
+.file-manager-rule-bar .input-group-prepend .btn {
+ position: relative; /* 添加相对定位以应用z-index */
+ z-index: 2; /* 确保边框在顶层 */
+ border-top-left-radius: 6px !important; /* 保持左上角圆角 */
+ border-bottom-left-radius: 6px !important; /* 保持左下角圆角 */
+ border-top-right-radius: 0 !important; /* 去除右上角圆角 */
+ border-bottom-right-radius: 0 !important; /* 去除右下角圆角 */
+ border-right: 1px solid var(--dark-text-color) !important; /* 确保右边框显示 */
+}
+
+/* 文件整理页面匹配表达式输入框左边框与正则命名按钮右边框重叠 */
+.file-manager-rule-bar .input-group-prepend + input.file-manager-input {
+ margin-left: 0px;
+}
+
+/* 文件整理页面激活的输入框边框置顶 */
+.file-manager-rule-bar .file-manager-input:focus {
+ position: relative;
+ z-index: 3;
+}
+
+/* 仅影响文件选择模态框表格的表头和单元格高度,减少5px */
+#fileSelectModal .table th {
+ padding-top: 4.5px !important; /* 原7px,减少2.5px */
+ padding-bottom: 4px !important; /* 原6.5px,减少2.5px */
+ height: 35px !important; /* 原40px,减少5px */
+ line-height: 19px !important; /* 原24px,减少5px */
+}
+
+#fileSelectModal .table td {
+ padding-top: 3px !important; /* 原5.5px,减少2.5px */
+ padding-bottom: 4.5px !important; /* 原7px,减少2.5px */
+ height: 35px !important; /* 原40px,减少5px */
+ line-height: 19px !important; /* 原24px,减少5px */
+}
+
+#fileSelectModal .table td {
+ vertical-align: middle !important;
+ position: relative;
+}
+
+#fileSelectModal .table td > *:not(.expand-button) {
+ position: relative;
+ top: 3.5px;
+}
+
+#fileSelectModal .table td.col-size,
+#fileSelectModal .table td.col-date {
+ padding-top: 6px !important;
+}
+
+#fileSelectModal .table td.col-rename,
+#fileSelectModal .table td.col-action {
+ padding-top: 2.5px !important;
+}
+
+#fileSelectModal[data-modal-type="preview"] .table td.col-rename {
+ padding-top: 3px !important;
+}
+
+.selectable-files .bi-file-earmark {
+ font-size: 1.06rem; /* 比模态框的0.95rem大一些 */
+ margin-right: 5px; /* 可适当调整间距 */
+ position: relative;
+ top: 1px; /* 可微调垂直对齐 */
+ left: -1px; /* 可微调水平对齐 */
+}
+
+.bi-folder-fill {
+ font-size: 1.06rem; /* 比模态框的0.95rem大一些 */
+ margin-right: 5px; /* 可适当调整间距 */
+ position: relative;
+ top: 1px; /* 可微调垂直对齐 */
+ left: -1px; /* 可微调水平对齐 */
+ color: #ffc107; /* 保持黄色 */
+}
+
+/* 文件整理页面无法识别剧集编号样式 */
+#fileSelectModal[data-modal-type="preview-filemanager"] .episode-number-text {
+ position: relative;
+ top: 1.5px; /* 或你想要的像素 */
+ display: inline-block;
+}
+
+/* 任务配置页面无法识别剧集编号样式 */
+#fileSelectModal[data-modal-type="preview"] .episode-number-text {
+ position: relative;
+ top: 1px;
+ display: inline-block;
+ left: 2px;
+}
+
+/* 文件整理页面无法识别剧集编号前面的 × 样式 */
+#fileSelectModal[data-modal-type="preview-filemanager"] .episode-x {
+ position: relative;
+ top: 0.5px;
+ display: inline-block;
+ margin-right: 2px;
+}
+
+/* 文件整理页面命名预览模式下的绿色重命名文本上移0.5px */
+#fileSelectModal[data-modal-type="preview-filemanager"] .table td.col-rename.text-success > * {
+ position: relative;
+ top: 3px !important; /* 原来是3px,上移0.5px */
+}
+
+/* 文件整理页面命名预览模式下的取消按钮样式 */
+#fileSelectModal[data-modal-type="preview-filemanager"] .modal-footer .btn-cancel {
+ background-color: var(--button-gray-background-color) !important;
+ border-color: var(--button-gray-background-color) !important;
+ color: var(--dark-text-color) !important;
+}
+#fileSelectModal[data-modal-type="preview-filemanager"] .modal-footer .btn-cancel:hover {
+ background-color: #e0e2e6 !important;
+ border-color: #e0e2e6 !important;
+ color: var(--dark-text-color) !important;
+}
+
+@media (max-width: 767.98px) {
+ .file-manager-rule-bar-responsive {
+ display: flex;
+ flex-direction: column;
+ align-items: stretch;
+ width: 100%;
+ }
+ .file-manager-rule-bar-responsive .rule-row {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ margin-bottom: 8px;
+ gap: 8px;
+ width: 100%;
+ }
+ .file-manager-rule-bar-responsive .rule-row:last-child {
+ margin-bottom: 0;
+ }
+ .file-manager-rule-bar-responsive .rule-row input,
+ .file-manager-rule-bar-responsive .rule-row button,
+ .file-manager-rule-bar-responsive .rule-row .input-group-text,
+ .file-manager-rule-bar-responsive .rule-row label {
+ flex: 1 1 0;
+ min-width: 0;
+ }
+ .file-manager-rule-bar-responsive .rule-row .input-group-prepend,
+ .file-manager-rule-bar-responsive .rule-row .input-group-text {
+ flex: 0 0 auto;
+ }
+ .file-manager-rule-bar-responsive .filter-label {
+ flex: 0 0 auto;
+ margin-right: 4px;
+ white-space: nowrap;
+ }
+}
+
+.file-manager-rule-bar-responsive { display: none; }
+.file-manager-rule-bar { display: flex; }
+@media (max-width: 767.98px) {
+ .file-manager-rule-bar { display: none !important; }
+ .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; }
+ /* 含文件夹样式调整 */
+ .file-manager-rule-bar-responsive .input-group-text.file-folder-rounded {
+ border-top-left-radius: 0 !important; /* 左侧不要圆角 */
+ border-bottom-left-radius: 0 !important; /* 左侧不要圆角 */
+ border-top-right-radius: 6px !important; /* 右侧圆角6px */
+ border-bottom-right-radius: 6px !important; /* 右侧圆角6px */
+ margin-left: -1px; /* 左边框与过滤规则输入框重叠 */
+ position: relative;
+ z-index: 1; /* 确保边框显示正确 */
+ }
+
+ /* 过滤规则输入框在含文件夹前面时去除右侧圆角 */
+ .file-manager-rule-bar-responsive .input-group .form-control + .input-group-text.file-folder-rounded {
+ border-left: 1px solid #ced4da; /* 确保左边框显示 */
+ }
+
+ /* 过滤规则输入框右侧圆角调整 */
+ .file-manager-rule-bar-responsive .input-group .form-control:not(:last-child) {
+ border-top-right-radius: 0 !important;
+ border-bottom-right-radius: 0 !important;
+ }
+ /* 预览并执行重命名按钮与含文件夹间距8px,且与上方input-group间距8px */
+ .file-manager-rule-bar-responsive .input-group-append .btn {
+ margin-left: 8px;
+ }
+ /* 只影响移动端的“预览并执行重命名”按钮上边距 */
+ .file-manager-rule-bar-responsive .batch-rename-btn {
+ margin-top: 0px; /* 你想要的上边距 */
+ }
+
+ /* 移动端预览并执行重命名按钮与含文件夹间距 */
+ .file-manager-rule-bar-responsive .input-group .batch-rename-btn {
+ margin-left: 8px !important;
+ }
+}
+@media (min-width: 768px) {
+ .file-manager-rule-bar { display: flex !important; }
+ .file-manager-rule-bar-responsive { display: none !important; }
+}
diff --git a/app/static/js/sort_file_by_name.js b/app/static/js/sort_file_by_name.js
new file mode 100644
index 0000000..4be5773
--- /dev/null
+++ b/app/static/js/sort_file_by_name.js
@@ -0,0 +1,155 @@
+// 与后端 quark_auto_save.py 的 sort_file_by_name 完全一致的排序逻辑
+// 用于前端文件列表排序
+
+function chineseToArabic(chinese) {
+ // 简单实现,支持一到一万
+ const cnNums = {
+ '零': 0, '一': 1, '二': 2, '两': 2, '三': 3, '四': 4, '五': 5, '六': 6, '七': 7, '八': 8, '九': 9,
+ '十': 10, '百': 100, '千': 1000, '万': 10000
+ };
+ let result = 0, unit = 1, num = 0;
+ for (let i = chinese.length - 1; i >= 0; i--) {
+ const char = chinese[i];
+ if (cnNums[char] >= 10) {
+ unit = cnNums[char];
+ if (unit === 10 && (i === 0 || cnNums[chinese[i - 1]] === undefined)) {
+ num = 1;
+ }
+ } else if (cnNums[char] !== undefined) {
+ num = cnNums[char];
+ result += num * unit;
+ }
+ }
+ return result || null;
+}
+
+function sortFileByName(file) {
+ // 兼容 dict 或字符串
+ let filename = typeof file === 'object' ? (file.file_name || '') : file;
+ let update_time = typeof file === 'object' ? (file.updated_at || 0) : 0;
+ let file_name_without_ext = filename.replace(/\.[^/.]+$/, '');
+ let date_value = Infinity, episode_value = Infinity, segment_value = 0;
+
+ // 1. 日期提取
+ let match;
+ // YYYY-MM-DD
+ match = filename.match(/((?:19|20)\d{2})[-./\s](\d{1,2})[-./\s](\d{1,2})/);
+ if (match) {
+ date_value = parseInt(match[1]) * 10000 + parseInt(match[2]) * 100 + parseInt(match[3]);
+ }
+ // YY-MM-DD
+ if (date_value === Infinity) {
+ match = filename.match(/((?:19|20)?\d{2})[-./\s](\d{1,2})[-./\s](\d{1,2})/);
+ if (match && match[1].length === 2) {
+ let year = parseInt('20' + match[1]);
+ date_value = year * 10000 + parseInt(match[2]) * 100 + parseInt(match[3]);
+ }
+ }
+ // YYYYMMDD
+ if (date_value === Infinity) {
+ match = filename.match(/((?:19|20)\d{2})(\d{2})(\d{2})/);
+ if (match) {
+ date_value = parseInt(match[1]) * 10000 + parseInt(match[2]) * 100 + parseInt(match[3]);
+ }
+ }
+ // YYMMDD
+ if (date_value === Infinity) {
+ match = filename.match(/(? 12) [month, day] = [day, month];
+ date_value = year * 10000 + month * 100 + day;
+ }
+ }
+ // MM-DD
+ if (date_value === Infinity) {
+ match = filename.match(/(? 12) [month, day] = [day, month];
+ date_value = 20000000 + month * 100 + day;
+ }
+ }
+
+ // 2. 期数/集数
+ // 第X期/集/话
+ match = filename.match(/第(\d+)[期集话]/);
+ if (match) episode_value = parseInt(match[1]);
+ // 第[中文数字]期/集/话
+ if (episode_value === Infinity) {
+ match = filename.match(/第([一二三四五六七八九十百千万零两]+)[期集话]/);
+ if (match) {
+ let arabic = chineseToArabic(match[1]);
+ if (arabic !== null) episode_value = arabic;
+ }
+ }
+ // X集/期/话
+ if (episode_value === Infinity) {
+ match = filename.match(/(\d+)[期集话]/);
+ if (match) episode_value = parseInt(match[1]);
+ }
+ // [中文数字]集/期/话
+ if (episode_value === Infinity) {
+ match = filename.match(/([一二三四五六七八九十百千万零两]+)[期集话]/);
+ if (match) {
+ let arabic = chineseToArabic(match[1]);
+ if (arabic !== null) episode_value = arabic;
+ }
+ }
+ // S01E01
+ if (episode_value === Infinity) {
+ match = filename.match(/[Ss](\d+)[Ee](\d+)/);
+ if (match) episode_value = parseInt(match[2]);
+ }
+ // E01/EP01
+ if (episode_value === Infinity) {
+ match = filename.match(/[Ee][Pp]?(\d+)/);
+ if (match) episode_value = parseInt(match[1]);
+ }
+ // 1x01
+ if (episode_value === Infinity) {
+ match = filename.match(/(\d+)[Xx](\d+)/);
+ if (match) episode_value = parseInt(match[2]);
+ }
+ // [数字]或【数字】
+ if (episode_value === Infinity) {
+ match = filename.match(/\[(\d+)\]|【(\d+)】/);
+ if (match) episode_value = parseInt(match[1] || match[2]);
+ }
+ // 纯数字文件名
+ if (episode_value === Infinity) {
+ if (/^\d+$/.test(file_name_without_ext)) {
+ episode_value = parseInt(file_name_without_ext);
+ } else {
+ match = filename.match(/(\d+)/);
+ if (match) episode_value = parseInt(match[1]);
+ }
+ }
+
+ // 3. 上中下
+ if (/[上][集期话部篇]?|[集期话部篇]上/.test(filename)) segment_value = 1;
+ else if (/[中][集期话部篇]?|[集期话部篇]中/.test(filename)) segment_value = 2;
+ else if (/[下][集期话部篇]?|[集期话部篇]下/.test(filename)) segment_value = 3;
+
+ return [date_value, episode_value, segment_value, update_time];
+}
+
+// 用法:
+// arr.sort((a, b) => {
+// const ka = sortFileByName(a), kb = sortFileByName(b);
+// for (let i = 0; i < ka.length; ++i) {
+// if (ka[i] !== kb[i]) return ka[i] > kb[i] ? 1 : -1;
+// }
+// return 0;
+// });
\ No newline at end of file
diff --git a/app/templates/index.html b/app/templates/index.html
index 3ee4995..fed8fb2 100644
--- a/app/templates/index.html
+++ b/app/templates/index.html
@@ -16,6 +16,7 @@
+
diff --git a/quark_auto_save.py b/quark_auto_save.py
index 842ec15..83cbf69 100644
--- a/quark_auto_save.py
+++ b/quark_auto_save.py
@@ -229,6 +229,9 @@ def extract_episode_number(filename, episode_patterns=None, config_data=None):
Returns:
int: 提取到的剧集号,如果无法提取则返回None
"""
+ # 首先去除文件扩展名
+ file_name_without_ext = os.path.splitext(filename)[0]
+
# 预处理:排除文件名中可能是日期的部分,避免误识别
date_patterns = [
# YYYY-MM-DD 或 YYYY.MM.DD 或 YYYY/MM/DD 或 YYYY MM DD格式(四位年份)
@@ -245,10 +248,10 @@ def extract_episode_number(filename, episode_patterns=None, config_data=None):
r'(?