mirror of
https://github.com/Cp0204/quark-auto-save.git
synced 2026-01-15 17:00:43 +08:00
commit
57dbccf418
93
app/run.py
93
app/run.py
@ -27,6 +27,7 @@ import os
|
|||||||
import re
|
import re
|
||||||
import random
|
import random
|
||||||
import time
|
import time
|
||||||
|
import treelib
|
||||||
|
|
||||||
parent_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
parent_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
||||||
sys.path.insert(0, parent_dir)
|
sys.path.insert(0, parent_dir)
|
||||||
@ -35,19 +36,41 @@ from quark_auto_save import Config, format_bytes
|
|||||||
|
|
||||||
# 添加导入全局extract_episode_number和sort_file_by_name函数
|
# 添加导入全局extract_episode_number和sort_file_by_name函数
|
||||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
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
|
from quark_auto_save import extract_episode_number, sort_file_by_name, chinese_to_arabic, is_date_format
|
||||||
|
|
||||||
# 导入数据库模块
|
# 导入数据库模块
|
||||||
try:
|
try:
|
||||||
from app.sdk.db import RecordDB
|
# 先尝试相对导入
|
||||||
|
from sdk.db import RecordDB
|
||||||
except ImportError:
|
except ImportError:
|
||||||
# 如果没有数据库模块,定义一个空类
|
try:
|
||||||
class RecordDB:
|
# 如果相对导入失败,尝试从app包导入
|
||||||
def __init__(self, *args, **kwargs):
|
from app.sdk.db import RecordDB
|
||||||
pass
|
except ImportError:
|
||||||
|
# 如果没有数据库模块,定义一个空类
|
||||||
def get_records(self, *args, **kwargs):
|
class RecordDB:
|
||||||
return {"records": [], "pagination": {"total_records": 0, "total_pages": 0, "current_page": 1, "page_size": 20}}
|
def __init__(self, *args, **kwargs):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_records(self, *args, **kwargs):
|
||||||
|
return {"records": [], "pagination": {"total_records": 0, "total_pages": 0, "current_page": 1, "page_size": 20}}
|
||||||
|
|
||||||
|
# 导入工具函数
|
||||||
|
try:
|
||||||
|
# 先尝试相对导入
|
||||||
|
from sdk.utils import format_bytes, get_file_icon, format_file_display
|
||||||
|
except ImportError:
|
||||||
|
try:
|
||||||
|
# 如果相对导入失败,尝试从app包导入
|
||||||
|
from app.sdk.utils import format_bytes, get_file_icon, format_file_display
|
||||||
|
except ImportError:
|
||||||
|
# 如果导入失败,使用默认实现或从quark_auto_save导入
|
||||||
|
# format_bytes已从quark_auto_save导入
|
||||||
|
def get_file_icon(file_name, is_dir=False):
|
||||||
|
return "📄" if not is_dir else "📁"
|
||||||
|
|
||||||
|
def format_file_display(prefix, icon, name):
|
||||||
|
return f"{prefix}{icon} {name}"
|
||||||
|
|
||||||
|
|
||||||
def get_app_ver():
|
def get_app_ver():
|
||||||
@ -463,37 +486,6 @@ def get_task_suggestions():
|
|||||||
return jsonify({"success": True, "message": f"error: {str(e)}"})
|
return jsonify({"success": True, "message": f"error: {str(e)}"})
|
||||||
|
|
||||||
|
|
||||||
# 添加函数,与主程序保持一致
|
|
||||||
def is_date_format(number_str):
|
|
||||||
"""
|
|
||||||
判断一个纯数字字符串是否可能是日期格式
|
|
||||||
支持的格式:YYYYMMDD, MMDD
|
|
||||||
"""
|
|
||||||
# 判断YYYYMMDD格式 (8位数字)
|
|
||||||
if len(number_str) == 8 and number_str.startswith('20'):
|
|
||||||
year = int(number_str[:4])
|
|
||||||
month = int(number_str[4:6])
|
|
||||||
day = int(number_str[6:8])
|
|
||||||
|
|
||||||
# 简单检查月份和日期是否有效
|
|
||||||
if 1 <= month <= 12 and 1 <= day <= 31:
|
|
||||||
# 可能是日期格式
|
|
||||||
return True
|
|
||||||
|
|
||||||
# 判断MMDD格式 (4位数字)
|
|
||||||
elif len(number_str) == 4:
|
|
||||||
month = int(number_str[:2])
|
|
||||||
day = int(number_str[2:4])
|
|
||||||
|
|
||||||
# 简单检查月份和日期是否有效
|
|
||||||
if 1 <= month <= 12 and 1 <= day <= 31:
|
|
||||||
# 可能是日期格式
|
|
||||||
return True
|
|
||||||
|
|
||||||
# 其他长度的纯数字不视为日期格式
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
# 获取分享详情接口
|
# 获取分享详情接口
|
||||||
@app.route("/get_share_detail", methods=["GET", "POST"])
|
@app.route("/get_share_detail", methods=["GET", "POST"])
|
||||||
def get_share_detail():
|
def get_share_detail():
|
||||||
@ -599,6 +591,22 @@ def get_share_detail():
|
|||||||
episode_pattern = regex.get("episode_naming")
|
episode_pattern = regex.get("episode_naming")
|
||||||
episode_patterns = regex.get("episode_patterns", [])
|
episode_patterns = regex.get("episode_patterns", [])
|
||||||
|
|
||||||
|
# 添加中文数字匹配模式
|
||||||
|
chinese_patterns = [
|
||||||
|
{"regex": r'第([一二三四五六七八九十百千万零两]+)集'},
|
||||||
|
{"regex": r'第([一二三四五六七八九十百千万零两]+)期'},
|
||||||
|
{"regex": r'第([一二三四五六七八九十百千万零两]+)话'},
|
||||||
|
{"regex": r'([一二三四五六七八九十百千万零两]+)集'},
|
||||||
|
{"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):
|
def extract_episode_number_local(filename):
|
||||||
return extract_episode_number(filename, episode_patterns=episode_patterns)
|
return extract_episode_number(filename, episode_patterns=episode_patterns)
|
||||||
@ -672,7 +680,7 @@ def get_share_detail():
|
|||||||
if any(word in item['file_name'] for word in filterwords_list):
|
if any(word in item['file_name'] for word in filterwords_list):
|
||||||
item["filtered"] = True
|
item["filtered"] = True
|
||||||
|
|
||||||
# 为每个文件生成新文件名
|
# 为每个文件生成新文件名并存储剧集编号用于排序
|
||||||
for file in sorted_files:
|
for file in sorted_files:
|
||||||
if not file.get("filtered"):
|
if not file.get("filtered"):
|
||||||
# 获取文件扩展名
|
# 获取文件扩展名
|
||||||
@ -686,9 +694,12 @@ def get_share_detail():
|
|||||||
file["file_name_re"] = f"{episode_num:02d}{file_ext}"
|
file["file_name_re"] = f"{episode_num:02d}{file_ext}"
|
||||||
else:
|
else:
|
||||||
file["file_name_re"] = episode_pattern.replace("[]", f"{episode_num:02d}") + file_ext
|
file["file_name_re"] = episode_pattern.replace("[]", f"{episode_num:02d}") + file_ext
|
||||||
|
# 存储原始的剧集编号,用于数值排序
|
||||||
|
file["episode_number"] = episode_num
|
||||||
else:
|
else:
|
||||||
# 无法提取剧集号,标记为无法处理
|
# 无法提取剧集号,标记为无法处理
|
||||||
file["file_name_re"] = "❌ 无法识别剧集号"
|
file["file_name_re"] = "❌ 无法识别剧集号"
|
||||||
|
file["episode_number"] = 9999999 # 给一个很大的值,确保排在最后
|
||||||
|
|
||||||
return share_detail
|
return share_detail
|
||||||
else:
|
else:
|
||||||
|
|||||||
@ -849,6 +849,8 @@ select.form-control {
|
|||||||
box-sizing: border-box !important;
|
box-sizing: border-box !important;
|
||||||
border-top-left-radius: 0 !important; /* 移除左上角圆角 */
|
border-top-left-radius: 0 !important; /* 移除左上角圆角 */
|
||||||
border-bottom-left-radius: 0 !important; /* 移除左下角圆角 */
|
border-bottom-left-radius: 0 !important; /* 移除左下角圆角 */
|
||||||
|
border-top-right-radius: 0 !important; /* 移除右上角圆角 */
|
||||||
|
border-bottom-right-radius: 0 !important; /* 移除右下角圆角 */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 为确保输入组中select元素的边角正确 */
|
/* 为确保输入组中select元素的边角正确 */
|
||||||
@ -4099,3 +4101,31 @@ table.selectable-records .expand-button:hover {
|
|||||||
.task:hover .btn.btn-block.text-left {
|
.task:hover .btn.btn-block.text-left {
|
||||||
color: var(--focus-border-color);
|
color: var(--focus-border-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
select.task-filter-select,
|
||||||
|
.task-filter-select {
|
||||||
|
border-top-right-radius: 0 !important;
|
||||||
|
border-bottom-right-radius: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-group .form-control:focus {
|
||||||
|
z-index: 3;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.input-group .form-control:focus + .input-group-append .btn,
|
||||||
|
.input-group .form-control:focus + .input-group-append .btn:focus {
|
||||||
|
border-left-color: #2563eb !important; /* 激活时的边框色 */
|
||||||
|
box-shadow: none !important;
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 添加任务名称悬停样式 */
|
||||||
|
.task-name-hover {
|
||||||
|
cursor: pointer;
|
||||||
|
transition: color 0.2s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-name-hover:hover {
|
||||||
|
color: var(--focus-border-color) !important;
|
||||||
|
}
|
||||||
|
|||||||
@ -115,6 +115,66 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 添加中文数字转阿拉伯数字的函数
|
||||||
|
function chineseToArabic(chinese) {
|
||||||
|
if (!chinese) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 数字映射
|
||||||
|
const digitMap = {
|
||||||
|
'零': 0, '一': 1, '二': 2, '三': 3, '四': 4,
|
||||||
|
'五': 5, '六': 6, '七': 7, '八': 8, '九': 9,
|
||||||
|
'两': 2
|
||||||
|
};
|
||||||
|
|
||||||
|
// 单位映射
|
||||||
|
const unitMap = {
|
||||||
|
'十': 10,
|
||||||
|
'百': 100,
|
||||||
|
'千': 1000,
|
||||||
|
'万': 10000
|
||||||
|
};
|
||||||
|
|
||||||
|
// 如果是单个字符,直接返回对应数字
|
||||||
|
if (chinese.length === 1) {
|
||||||
|
if (chinese === '十') return 10;
|
||||||
|
return digitMap[chinese];
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = 0;
|
||||||
|
let section = 0;
|
||||||
|
let number = 0;
|
||||||
|
|
||||||
|
// 从左向右处理
|
||||||
|
for (let i = 0; i < chinese.length; i++) {
|
||||||
|
const char = chinese[i];
|
||||||
|
|
||||||
|
if (char in digitMap) {
|
||||||
|
number = digitMap[char];
|
||||||
|
} else if (char in unitMap) {
|
||||||
|
const unit = unitMap[char];
|
||||||
|
// 如果前面没有数字,默认为1,例如"十"表示1*10=10
|
||||||
|
section += (number || 1) * unit;
|
||||||
|
number = 0;
|
||||||
|
|
||||||
|
// 如果是万级单位,累加到结果并重置section
|
||||||
|
if (unit === 10000) {
|
||||||
|
result += section;
|
||||||
|
section = 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 非法字符
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加上最后的数字和小节
|
||||||
|
result += section + number;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
@ -523,7 +583,7 @@
|
|||||||
<option value="">全部任务</option>
|
<option value="">全部任务</option>
|
||||||
<option v-for="task in taskNames" :value="task" v-html="task"></option>
|
<option v-for="task in taskNames" :value="task" v-html="task"></option>
|
||||||
</select>
|
</select>
|
||||||
<i class="bi bi-chevron-down select-arrow" style="position: absolute; pointer-events: none; color: var(--dark-text-color);"></i>
|
<!-- <i class="bi bi-chevron-down select-arrow" style="position: absolute; pointer-events: none; color: var(--dark-text-color);"></i> -->
|
||||||
</div>
|
</div>
|
||||||
<div class="input-group-append">
|
<div class="input-group-append">
|
||||||
<button type="button" class="btn btn-outline-secondary filter-btn-square" @click="clearData('taskDirSelected')"><i class="bi bi-x-lg"></i></button>
|
<button type="button" class="btn btn-outline-secondary filter-btn-square" @click="clearData('taskDirSelected')"><i class="bi bi-x-lg"></i></button>
|
||||||
@ -735,7 +795,7 @@
|
|||||||
<option value="">全部任务</option>
|
<option value="">全部任务</option>
|
||||||
<option v-for="task in historyTasks" :value="task" v-html="task"></option>
|
<option v-for="task in historyTasks" :value="task" v-html="task"></option>
|
||||||
</select>
|
</select>
|
||||||
<i class="bi bi-chevron-down select-arrow" style="position: absolute; pointer-events: none; color: var(--dark-text-color);"></i>
|
<!-- <i class="bi bi-chevron-down select-arrow" style="position: absolute; pointer-events: none; color: var(--dark-text-color);"></i> -->
|
||||||
</div>
|
</div>
|
||||||
<div class="input-group-append">
|
<div class="input-group-append">
|
||||||
<button type="button" class="btn btn-outline-secondary filter-btn-square" @click="clearData('historyTaskSelected')"><i class="bi bi-x-lg"></i></button>
|
<button type="button" class="btn btn-outline-secondary filter-btn-square" @click="clearData('historyTaskSelected')"><i class="bi bi-x-lg"></i></button>
|
||||||
@ -768,13 +828,15 @@
|
|||||||
<div v-if="!record._expandedFields || !record._expandedFields.includes('task_name')"
|
<div v-if="!record._expandedFields || !record._expandedFields.includes('task_name')"
|
||||||
class="text-truncate"
|
class="text-truncate"
|
||||||
:title="record.task_name"
|
:title="record.task_name"
|
||||||
v-check-overflow="index + '|task_name'">
|
v-check-overflow="index + '|task_name'"
|
||||||
|
:class="{'task-name-hover': true}"
|
||||||
|
@click.stop="filterByTaskName(record.task_name, $event)">
|
||||||
{{ record.task_name }}
|
{{ record.task_name }}
|
||||||
</div>
|
</div>
|
||||||
<div class="expand-button" v-if="isTextTruncated(record.task_name, index, 'task_name')" @click.stop="toggleExpand(index, 'task_name', $event)">
|
<div class="expand-button" v-if="isTextTruncated(record.task_name, index, 'task_name')" @click.stop="toggleExpand(index, 'task_name', $event)">
|
||||||
<i :class="record._expandedFields && record._expandedFields.length > 0 ? 'bi bi-chevron-up' : 'bi bi-chevron-down'"></i>
|
<i :class="record._expandedFields && record._expandedFields.length > 0 ? 'bi bi-chevron-up' : 'bi bi-chevron-down'"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="expanded-text" v-if="record._expandedFields && record._expandedFields.includes('task_name')">
|
<div class="expanded-text task-name-hover" v-if="record._expandedFields && record._expandedFields.includes('task_name')" @click.stop="filterByTaskName(record.task_name, $event)">
|
||||||
{{ record.task_name }}
|
{{ record.task_name }}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
@ -1788,13 +1850,10 @@
|
|||||||
if (match) {
|
if (match) {
|
||||||
separatorMatch = match;
|
separatorMatch = match;
|
||||||
// 根据不同的格式,确定季序号的位置
|
// 根据不同的格式,确定季序号的位置
|
||||||
if (match[3] && match[3].match(/[一二三四五六七八九十零]/)) {
|
if (match[3] && match[3].match(/[一二三四五六七八九十百千万零两]/)) {
|
||||||
// 将中文数字转换为阿拉伯数字
|
// 将中文数字转换为阿拉伯数字
|
||||||
const chineseToNumber = {
|
const arabicNumber = chineseToArabic(match[3]);
|
||||||
'零': '0', '一': '1', '二': '2', '三': '3', '四': '4',
|
seasonNumber = arabicNumber !== null ? String(arabicNumber) : '01';
|
||||||
'五': '5', '六': '6', '七': '7', '八': '8', '九': '9', '十': '10'
|
|
||||||
};
|
|
||||||
seasonNumber = chineseToNumber[match[3]] || '01';
|
|
||||||
} else {
|
} else {
|
||||||
seasonNumber = match[3] || '01';
|
seasonNumber = match[3] || '01';
|
||||||
}
|
}
|
||||||
@ -1891,18 +1950,15 @@
|
|||||||
if (pathParts.length > 0) {
|
if (pathParts.length > 0) {
|
||||||
const lastPart = pathParts[pathParts.length - 1];
|
const lastPart = pathParts[pathParts.length - 1];
|
||||||
// 匹配中文季序号格式
|
// 匹配中文季序号格式
|
||||||
const chineseSeasonMatch = lastPart.match(/^(.*?)([\s\.\-_]+)第([一二三四五六七八九十零]+)季$/);
|
const chineseSeasonMatch = lastPart.match(/^(.*?)([\s\.\-_]+)第([一二三四五六七八九十百千万零两]+)季$/);
|
||||||
if (chineseSeasonMatch) {
|
if (chineseSeasonMatch) {
|
||||||
const showName = chineseSeasonMatch[1].trim();
|
const showName = chineseSeasonMatch[1].trim();
|
||||||
const separator = chineseSeasonMatch[2];
|
const separator = chineseSeasonMatch[2];
|
||||||
const chineseSeason = chineseSeasonMatch[3];
|
const chineseSeason = chineseSeasonMatch[3];
|
||||||
|
|
||||||
// 将中文数字转换为阿拉伯数字
|
// 将中文数字转换为阿拉伯数字
|
||||||
const chineseToNumber = {
|
const arabicNumber = chineseToArabic(chineseSeason);
|
||||||
'零': '0', '一': '1', '二': '2', '三': '3', '四': '4',
|
const seasonNumber = arabicNumber !== null ? String(arabicNumber) : '1';
|
||||||
'五': '5', '六': '6', '七': '7', '八': '8', '九': '9', '十': '10'
|
|
||||||
};
|
|
||||||
const seasonNumber = chineseToNumber[chineseSeason] || '1';
|
|
||||||
|
|
||||||
// 更新最末级目录为标准格式
|
// 更新最末级目录为标准格式
|
||||||
pathParts[pathParts.length - 1] = showName + separator + 'S' + seasonNumber.padStart(2, '0');
|
pathParts[pathParts.length - 1] = showName + separator + 'S' + seasonNumber.padStart(2, '0');
|
||||||
@ -2345,6 +2401,9 @@
|
|||||||
},
|
},
|
||||||
selectSuggestion(index, suggestion) {
|
selectSuggestion(index, suggestion) {
|
||||||
this.smart_param.showSuggestions = false;
|
this.smart_param.showSuggestions = false;
|
||||||
|
// 确保显示的是选择需转存的文件夹界面,而不是命名预览界面
|
||||||
|
this.fileSelect.previewRegex = false;
|
||||||
|
this.fileSelect.selectDir = true;
|
||||||
this.showShareSelect(index, suggestion.shareurl);
|
this.showShareSelect(index, suggestion.shareurl);
|
||||||
},
|
},
|
||||||
addMagicRegex() {
|
addMagicRegex() {
|
||||||
@ -2419,7 +2478,7 @@
|
|||||||
alert('删除项目出错: ' + (error.response?.data?.message || error.message));
|
alert('删除项目出错: ' + (error.response?.data?.message || error.message));
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
getSavepathDetail(params = 0) {
|
getSavepathDetail(params = 0, retryCount = 0, maxRetries = 3) {
|
||||||
if (params === "" || params === null || params === undefined) {
|
if (params === "" || params === null || params === undefined) {
|
||||||
// 为空字符串时直接使用根目录fid
|
// 为空字符串时直接使用根目录fid
|
||||||
params = 0;
|
params = 0;
|
||||||
@ -2440,8 +2499,18 @@
|
|||||||
}
|
}
|
||||||
this.modalLoading = false;
|
this.modalLoading = false;
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
this.fileSelect.error = "获取文件夹列表失败";
|
// 如果还有重试次数,则进行重试
|
||||||
this.modalLoading = false;
|
if (retryCount < maxRetries) {
|
||||||
|
console.log(`获取文件夹列表失败,正在进行第 ${retryCount + 1} 次重试...`);
|
||||||
|
// 短暂延迟后重试
|
||||||
|
setTimeout(() => {
|
||||||
|
this.getSavepathDetail(params, retryCount + 1, maxRetries);
|
||||||
|
}, 1000); // 1秒后重试
|
||||||
|
} else {
|
||||||
|
// 超过最大重试次数,显示错误信息
|
||||||
|
this.fileSelect.error = "获取文件夹列表失败,请关闭窗口再试一次";
|
||||||
|
this.modalLoading = false;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
showSavepathSelect(index) {
|
showSavepathSelect(index) {
|
||||||
@ -2469,7 +2538,7 @@
|
|||||||
this.getSavepathDetail(savepath);
|
this.getSavepathDetail(savepath);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getShareDetail() {
|
getShareDetail(retryCount = 0, maxRetries = 3) {
|
||||||
this.modalLoading = true;
|
this.modalLoading = true;
|
||||||
axios.post('/get_share_detail', {
|
axios.post('/get_share_detail', {
|
||||||
shareurl: this.fileSelect.shareurl,
|
shareurl: this.fileSelect.shareurl,
|
||||||
@ -2515,8 +2584,18 @@
|
|||||||
}
|
}
|
||||||
this.modalLoading = false;
|
this.modalLoading = false;
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
this.fileSelect.error = "获取文件夹列表失败";
|
// 如果还有重试次数,则进行重试
|
||||||
this.modalLoading = false;
|
if (retryCount < maxRetries) {
|
||||||
|
console.log(`获取文件夹列表失败,正在进行第 ${retryCount + 1} 次重试...`);
|
||||||
|
// 短暂延迟后重试
|
||||||
|
setTimeout(() => {
|
||||||
|
this.getShareDetail(retryCount + 1, maxRetries);
|
||||||
|
}, 1000); // 1秒后重试
|
||||||
|
} else {
|
||||||
|
// 超过最大重试次数,显示错误信息
|
||||||
|
this.fileSelect.error = "获取文件夹列表失败,请关闭窗口再试一次";
|
||||||
|
this.modalLoading = false;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
showShareSelect(index, shareurl = null) {
|
showShareSelect(index, shareurl = null) {
|
||||||
@ -2550,7 +2629,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
$('#fileSelectModal').modal('toggle');
|
$('#fileSelectModal').modal('toggle');
|
||||||
this.getShareDetail();
|
// 调用getShareDetail时不传递任何参数,使用默认的重试机制
|
||||||
|
this.getShareDetail(0, 3);
|
||||||
|
|
||||||
// 命名预览模式下,确保在模态框显示后检查滚动条状态
|
// 命名预览模式下,确保在模态框显示后检查滚动条状态
|
||||||
if (this.fileSelect.previewRegex) {
|
if (this.fileSelect.previewRegex) {
|
||||||
@ -2563,7 +2643,8 @@
|
|||||||
path = { fid: fid, name: name }
|
path = { fid: fid, name: name }
|
||||||
if (this.fileSelect.selectShare) {
|
if (this.fileSelect.selectShare) {
|
||||||
this.fileSelect.shareurl = this.getShareurl(this.fileSelect.shareurl, path);
|
this.fileSelect.shareurl = this.getShareurl(this.fileSelect.shareurl, path);
|
||||||
this.getShareDetail();
|
// 使用重试机制调用getShareDetail
|
||||||
|
this.getShareDetail(0, 3);
|
||||||
} else {
|
} else {
|
||||||
if (fid == "0") {
|
if (fid == "0") {
|
||||||
this.fileSelect.paths = []
|
this.fileSelect.paths = []
|
||||||
@ -2575,7 +2656,8 @@
|
|||||||
this.fileSelect.paths.push({ fid: fid, name: name })
|
this.fileSelect.paths.push({ fid: fid, name: name })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.getSavepathDetail(fid);
|
// 使用重试机制调用getSavepathDetail
|
||||||
|
this.getSavepathDetail(fid, 0, 3);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
selectCurrentFolder(addTaskname = false) {
|
selectCurrentFolder(addTaskname = false) {
|
||||||
@ -3156,14 +3238,13 @@
|
|||||||
},
|
},
|
||||||
// 文件选择模态框的排序方法
|
// 文件选择模态框的排序方法
|
||||||
sortFileList(field) {
|
sortFileList(field) {
|
||||||
// 如果点击了当前排序字段,则切换排序顺序
|
// 切换排序方向
|
||||||
if (this.fileSelect.sortBy === field) {
|
if (this.fileSelect.sortBy === field) {
|
||||||
this.fileSelect.sortOrder = this.fileSelect.sortOrder === 'asc' ? 'desc' : 'asc';
|
this.fileSelect.sortOrder = this.fileSelect.sortOrder === 'asc' ? 'desc' : 'asc';
|
||||||
} else {
|
} else {
|
||||||
// 如果点击了不同的字段,则设置为该字段并使用默认排序顺序
|
|
||||||
this.fileSelect.sortBy = field;
|
this.fileSelect.sortBy = field;
|
||||||
// 对于日期和重命名列使用降序,其他字段使用升序
|
// 默认降序(除了文件名外)
|
||||||
this.fileSelect.sortOrder = field === 'updated_at' || field === 'file_name_re' ? 'desc' : 'asc';
|
this.fileSelect.sortOrder = field === 'file_name' ? 'asc' : 'desc';
|
||||||
}
|
}
|
||||||
|
|
||||||
// 按选定字段和顺序对文件列表进行排序
|
// 按选定字段和顺序对文件列表进行排序
|
||||||
@ -3179,9 +3260,15 @@
|
|||||||
aValue = a.file_name.toLowerCase();
|
aValue = a.file_name.toLowerCase();
|
||||||
bValue = b.file_name.toLowerCase();
|
bValue = b.file_name.toLowerCase();
|
||||||
} else if (field === 'file_name_re') {
|
} else if (field === 'file_name_re') {
|
||||||
// 对于重命名列,使用重命名后的文件名进行排序
|
// 对于重命名列,优先使用episode_number进行数值排序(如果存在)
|
||||||
aValue = (a.file_name_re || '').toLowerCase();
|
if (a.episode_number !== undefined && b.episode_number !== undefined) {
|
||||||
bValue = (b.file_name_re || '').toLowerCase();
|
aValue = a.episode_number;
|
||||||
|
bValue = b.episode_number;
|
||||||
|
} else {
|
||||||
|
// 否则使用重命名后的文件名进行字符串排序
|
||||||
|
aValue = (a.file_name_re || '').toLowerCase();
|
||||||
|
bValue = (b.file_name_re || '').toLowerCase();
|
||||||
|
}
|
||||||
} else if (field === 'size') {
|
} else if (field === 'size') {
|
||||||
// 对于文件大小,使用数字进行排序
|
// 对于文件大小,使用数字进行排序
|
||||||
if (a.dir && b.dir) {
|
if (a.dir && b.dir) {
|
||||||
@ -3223,9 +3310,15 @@
|
|||||||
aValue = a.file_name.toLowerCase();
|
aValue = a.file_name.toLowerCase();
|
||||||
bValue = b.file_name.toLowerCase();
|
bValue = b.file_name.toLowerCase();
|
||||||
} else if (field === 'file_name_re') {
|
} else if (field === 'file_name_re') {
|
||||||
// 对于重命名列,使用重命名后的文件名进行排序
|
// 对于重命名列,优先使用episode_number进行数值排序(如果存在)
|
||||||
aValue = (a.file_name_re || '').toLowerCase();
|
if (a.episode_number !== undefined && b.episode_number !== undefined) {
|
||||||
bValue = (b.file_name_re || '').toLowerCase();
|
aValue = a.episode_number;
|
||||||
|
bValue = b.episode_number;
|
||||||
|
} else {
|
||||||
|
// 否则使用重命名后的文件名进行字符串排序
|
||||||
|
aValue = (a.file_name_re || '').toLowerCase();
|
||||||
|
bValue = (b.file_name_re || '').toLowerCase();
|
||||||
|
}
|
||||||
} else if (field === 'size') {
|
} else if (field === 'size') {
|
||||||
// 对于文件大小,使用数字进行排序
|
// 对于文件大小,使用数字进行排序
|
||||||
if (a.dir && b.dir) {
|
if (a.dir && b.dir) {
|
||||||
@ -3684,6 +3777,23 @@
|
|||||||
alert("刷新 AList 目录失败: " + (error.response?.data?.message || error.message || "未知错误"));
|
alert("刷新 AList 目录失败: " + (error.response?.data?.message || error.message || "未知错误"));
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
filterByTaskName(taskName, event) {
|
||||||
|
// 防止事件冒泡,避免触发行选择
|
||||||
|
if (event) {
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果当前已经筛选了该任务,则取消筛选
|
||||||
|
if (this.historyTaskSelected === taskName) {
|
||||||
|
this.historyTaskSelected = "";
|
||||||
|
} else {
|
||||||
|
// 设置任务筛选值
|
||||||
|
this.historyTaskSelected = taskName;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重新加载记录
|
||||||
|
this.loadHistoryRecords();
|
||||||
|
},
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -141,12 +141,30 @@ def sort_file_by_name(file):
|
|||||||
if match_chinese:
|
if match_chinese:
|
||||||
episode_value = int(match_chinese.group(1))
|
episode_value = int(match_chinese.group(1))
|
||||||
|
|
||||||
|
# 2.1.1 "第[中文数字]期/集/话" 格式
|
||||||
|
if episode_value == float('inf'):
|
||||||
|
match_chinese_num = re.search(r'第([一二三四五六七八九十百千万零两]+)[期集话]', filename)
|
||||||
|
if match_chinese_num:
|
||||||
|
chinese_num = match_chinese_num.group(1)
|
||||||
|
arabic_num = chinese_to_arabic(chinese_num)
|
||||||
|
if arabic_num is not None:
|
||||||
|
episode_value = arabic_num
|
||||||
|
|
||||||
# 2.2 "X集/期/话" 格式
|
# 2.2 "X集/期/话" 格式
|
||||||
if episode_value == float('inf'):
|
if episode_value == float('inf'):
|
||||||
match_chinese_simple = re.search(r'(\d+)[期集话]', filename)
|
match_chinese_simple = re.search(r'(\d+)[期集话]', filename)
|
||||||
if match_chinese_simple:
|
if match_chinese_simple:
|
||||||
episode_value = int(match_chinese_simple.group(1))
|
episode_value = int(match_chinese_simple.group(1))
|
||||||
|
|
||||||
|
# 2.2.1 "[中文数字]集/期/话" 格式
|
||||||
|
if episode_value == float('inf'):
|
||||||
|
match_chinese_simple_num = re.search(r'([一二三四五六七八九十百千万零两]+)[期集话]', filename)
|
||||||
|
if match_chinese_simple_num:
|
||||||
|
chinese_num = match_chinese_simple_num.group(1)
|
||||||
|
arabic_num = chinese_to_arabic(chinese_num)
|
||||||
|
if arabic_num is not None:
|
||||||
|
episode_value = arabic_num
|
||||||
|
|
||||||
# 2.3 S01E01格式
|
# 2.3 S01E01格式
|
||||||
if episode_value == float('inf'):
|
if episode_value == float('inf'):
|
||||||
match_s_e = re.search(r'[Ss](\d+)[Ee](\d+)', filename)
|
match_s_e = re.search(r'[Ss](\d+)[Ee](\d+)', filename)
|
||||||
@ -299,6 +317,16 @@ def extract_episode_number(filename, episode_patterns=None, config_data=None):
|
|||||||
r'_?(\d+)_?'
|
r'_?(\d+)_?'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# 添加中文数字匹配模式
|
||||||
|
chinese_patterns = [
|
||||||
|
r'第([一二三四五六七八九十百千万零两]+)集',
|
||||||
|
r'第([一二三四五六七八九十百千万零两]+)期',
|
||||||
|
r'第([一二三四五六七八九十百千万零两]+)话',
|
||||||
|
r'([一二三四五六七八九十百千万零两]+)集',
|
||||||
|
r'([一二三四五六七八九十百千万零两]+)期',
|
||||||
|
r'([一二三四五六七八九十百千万零两]+)话'
|
||||||
|
]
|
||||||
|
|
||||||
patterns = None
|
patterns = None
|
||||||
|
|
||||||
# 检查传入的episode_patterns参数
|
# 检查传入的episode_patterns参数
|
||||||
@ -328,7 +356,19 @@ def extract_episode_number(filename, episode_patterns=None, config_data=None):
|
|||||||
return episode_num
|
return episode_num
|
||||||
except:
|
except:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# 尝试匹配中文数字模式
|
||||||
|
for pattern_regex in chinese_patterns:
|
||||||
|
try:
|
||||||
|
match = re.search(pattern_regex, filename_without_dates)
|
||||||
|
if match:
|
||||||
|
chinese_num = match.group(1)
|
||||||
|
arabic_num = chinese_to_arabic(chinese_num)
|
||||||
|
if arabic_num is not None:
|
||||||
|
return arabic_num
|
||||||
|
except:
|
||||||
|
continue
|
||||||
|
|
||||||
# 如果从不含日期的文件名中没有找到剧集号,尝试从原始文件名中提取
|
# 如果从不含日期的文件名中没有找到剧集号,尝试从原始文件名中提取
|
||||||
# 这是为了兼容某些特殊情况,但要检查提取的数字不是日期
|
# 这是为了兼容某些特殊情况,但要检查提取的数字不是日期
|
||||||
file_name_without_ext = os.path.splitext(filename)[0]
|
file_name_without_ext = os.path.splitext(filename)[0]
|
||||||
@ -395,6 +435,71 @@ def is_date_format(number_str):
|
|||||||
# 其他格式不视为日期格式
|
# 其他格式不视为日期格式
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def chinese_to_arabic(chinese):
|
||||||
|
"""
|
||||||
|
将中文数字转换为阿拉伯数字
|
||||||
|
支持格式:一、二、三、四、五、六、七、八、九、十、百、千、万
|
||||||
|
以及:零、两(特殊处理为2)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
chinese: 中文数字字符串
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
int: 转换后的阿拉伯数字,如果无法转换则返回None
|
||||||
|
"""
|
||||||
|
if not chinese:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# 数字映射
|
||||||
|
digit_map = {
|
||||||
|
'零': 0, '一': 1, '二': 2, '三': 3, '四': 4,
|
||||||
|
'五': 5, '六': 6, '七': 7, '八': 8, '九': 9,
|
||||||
|
'两': 2
|
||||||
|
}
|
||||||
|
|
||||||
|
# 单位映射
|
||||||
|
unit_map = {
|
||||||
|
'十': 10,
|
||||||
|
'百': 100,
|
||||||
|
'千': 1000,
|
||||||
|
'万': 10000
|
||||||
|
}
|
||||||
|
|
||||||
|
# 如果是单个字符,直接返回对应数字
|
||||||
|
if len(chinese) == 1:
|
||||||
|
if chinese == '十':
|
||||||
|
return 10
|
||||||
|
return digit_map.get(chinese)
|
||||||
|
|
||||||
|
result = 0
|
||||||
|
section = 0
|
||||||
|
number = 0
|
||||||
|
|
||||||
|
# 从左向右处理
|
||||||
|
for i in range(len(chinese)):
|
||||||
|
char = chinese[i]
|
||||||
|
|
||||||
|
if char in digit_map:
|
||||||
|
number = digit_map[char]
|
||||||
|
elif char in unit_map:
|
||||||
|
unit = unit_map[char]
|
||||||
|
# 如果前面没有数字,默认为1,例如"十"表示1*10=10
|
||||||
|
section += (number or 1) * unit
|
||||||
|
number = 0
|
||||||
|
|
||||||
|
# 如果是万级单位,累加到结果并重置section
|
||||||
|
if unit == 10000:
|
||||||
|
result += section
|
||||||
|
section = 0
|
||||||
|
else:
|
||||||
|
# 非法字符
|
||||||
|
return None
|
||||||
|
|
||||||
|
# 加上最后的数字和小节
|
||||||
|
result += section + number
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
# 兼容青龙
|
# 兼容青龙
|
||||||
try:
|
try:
|
||||||
from treelib import Tree
|
from treelib import Tree
|
||||||
@ -3133,7 +3238,7 @@ def format_bytes(size_bytes: int) -> str:
|
|||||||
while size_bytes >= 1024 and i < len(units) - 1:
|
while size_bytes >= 1024 and i < len(units) - 1:
|
||||||
size_bytes /= 1024
|
size_bytes /= 1024
|
||||||
i += 1
|
i += 1
|
||||||
return f"{size_bytes:.2f}{units[i]}"
|
return f"{size_bytes:.2f} {units[i]}"
|
||||||
|
|
||||||
|
|
||||||
def do_sign(account):
|
def do_sign(account):
|
||||||
@ -4258,6 +4363,11 @@ def do_save(account, tasklist=[]):
|
|||||||
number_part = filename[len(prefix):].split(suffix)[0] if suffix else filename[len(prefix):]
|
number_part = filename[len(prefix):].split(suffix)[0] if suffix else filename[len(prefix):]
|
||||||
if number_part.isdigit():
|
if number_part.isdigit():
|
||||||
return int(number_part)
|
return int(number_part)
|
||||||
|
# 尝试转换中文数字
|
||||||
|
else:
|
||||||
|
arabic_num = chinese_to_arabic(number_part)
|
||||||
|
if arabic_num is not None:
|
||||||
|
return arabic_num
|
||||||
|
|
||||||
# 如果所有方法都失败,返回float('inf')
|
# 如果所有方法都失败,返回float('inf')
|
||||||
return float('inf')
|
return float('inf')
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user