quark-auto-save/app/templates/index.html

903 lines
43 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>夸克自动转存</title>
<!-- CSS -->
<link rel="stylesheet" href="./static/css/bootstrap.min.css">
<link rel="stylesheet" href="./static/css/bootstrap-icons.min.css">
<link rel="stylesheet" href="./static/css/dashboard.css">
<!-- Bootstrap JS -->
<script src="./static/js/jquery-3.5.1.slim.min.js"></script>
<script src="./static/js/bootstrap.bundle.min.js"></script>
<!-- Vue.js -->
<script src="./static/js/vue@2.js"></script>
<script src="./static/js/axios.min.js"></script>
<script src="./static/js/v-jsoneditor.min.js"></script>
</head>
<body>
<div id="app">
<nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0">
<a class="navbar-brand col-md-3 col-lg-2 mr-0 px-3" href="#"><i class="bi bi-clouds"></i> 夸克自动转存</a>
<button class="navbar-toggler position-absolute d-md-none collapsed" type="button" data-toggle="collapse" data-target="#sidebarMenu" aria-controls="sidebarMenu" aria-expanded="false">
<span class="navbar-toggler-icon"></span>
</button>
</nav>
<div class="container-fluid">
<nav id="sidebarMenu" class="col-md-3 col-lg-2 d-md-block bg-light sidebar collapse">
<div class="sidebar-sticky pt-3">
<ul class="nav flex-column">
<li class="nav-item">
<a class="nav-link" href="#" :class="{active: activeTab === 'config'}" @click="changeTab('config')">
<i class="bi bi-gear-fill"></i> 系统配置
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#" :class="{active: activeTab === 'tasklist'}" @click="changeTab('tasklist')">
<i class="bi bi-list-task"></i> 任务列表
</a>
</li>
<h6 class="sidebar-heading px-3 mt-4 mb-1 text-muted">dashboard</h6>
<li class="nav-item">
<a class="nav-link" href="/logout">
<i class="bi bi-box-arrow-right"></i></i> 退出
</a>
</li>
</ul>
<div class="text-center" style="position: absolute; bottom: 32px; width: 100%; font-size: small;">
<p><a class="" target="_blank" href="https://github.com/Cp0204/quark-auto-save/wiki"><i class="bi bi-wechat"></i> 使用交流</a></p>
<p><a target="_blank" href="https://github.com/Cp0204/quark-auto-save"><i class="bi bi-github"></i> quark-auto-save</a></p>
<p><span v-html="versionTips"></span></p>
</div>
</div>
</nav>
<main class="col-md-9 col-lg-10 ml-sm-auto">
<form @submit.prevent="saveConfig">
<div v-if="activeTab === 'config'">
<div class="row title">
<div class="col">
<h2><i class="bi bi-cookie"></i> Cookie</h2>
</div>
<div class="col text-right">
<button type="button" class="btn btn-outline-primary mb-3" @click="addCookie()">+</button>
</div>
</div>
<p>1. 所有账号执行签到,纯签到只需移动端参数即可!</p>
<p>2. 仅第一个账号执行转存,请自行确认顺序。<b>最好是手机验证码<a target="_blank" href="https://pan.quark.cn/">登录</a>CK比较完整</b>如需签到参数附在CK后面。</p>
<div v-for="(value, index) in formData.cookie" :key="index" class="input-group mb-2">
<input type="text" v-model="formData.cookie[index]" class="form-control" placeholder="打开 pan.quark.com 按 F12 抓取">
<div class="input-group-append">
<button type="button" class="btn btn-outline-danger" @click="removeCookie(index)">-</button>
</div>
</div>
<div class="row title">
<div class="col">
<h2 style="display: inline-block;"><i class="bi bi-clock"></i> 定时规则</h2>
<span class="badge badge-pill badge-light">
<a target="_blank" href="https://tool.lu/crontab/" title="CRON时间计算器">?</a>
</span>
</div>
</div>
<div class="input-group mt-2 mb-2">
<div class="input-group-prepend">
<span class="input-group-text">Crontab</span>
</div>
<input type="text" v-model="formData.crontab" class="form-control" placeholder="必填">
</div>
<div class="row title">
<div class="col">
<h2 style="display: inline-block;"><i class="bi bi-bell"></i> 通知</h2>
<span class="badge badge-pill badge-light">
<a href="https://github.com/Cp0204/quark-auto-save/wiki/通知推送服务配置" target="_blank" title="通知推送服务配置">?</a>
</span>
</div>
<div class="col text-right">
<button type="button" class="btn btn-outline-primary mb-3" @click="addPush()">+</button>
</div>
</div>
<div v-for="(value, key) in formData.push_config" :key="key" class="input-group mb-2">
<div class="input-group-prepend">
<span class="input-group-text" v-html="key"></span>
</div>
<div class="input-group-prepend" v-if="(key == 'DEER_KEY' || key == 'PUSH_KEY')">
<a type="button" class="btn btn-warning" target="_blank" href="https://sct.ftqq.com/r/13249" title="Server酱推荐计划"><i class="bi bi-award"></i></a>
</div>
<input type="text" v-model="formData.push_config[key]" class="form-control">
<div class="input-group-append">
<button type="button" class="btn btn-outline-danger" @click="removePush(key)">-</button>
</div>
</div>
<div class="row title" v-if="Object.keys(getAvailablePlugins(formData.plugins)).length">
<div class="col">
<h2 style="display: inline-block;"><i class="bi bi-plug"></i> 插件</h2>
<span class="badge badge-pill badge-light">
<a href="https://github.com/Cp0204/quark-auto-save/wiki/插件配置" target="_blank" title="插件配置">?</a>
</span>
</div>
</div>
<div v-for="(plugin, pluginName) in getAvailablePlugins(formData.plugins)" :key="pluginName">
<div class="form-group row mb-0" style="display:flex; align-items:center;">
<div data-toggle="collapse" :data-target="'#collapse_'+pluginName" aria-expanded="true" :aria-controls="'collapse_'+pluginName">
<div class="btn btn-block text-left">
<i class="bi bi-caret-right-fill"></i> <span v-html="`${pluginName}`"></span>
</div>
</div>
</div>
<div class="collapse ml-3" :id="'collapse_'+pluginName">
<div v-for="(value, key) in plugin" :key="key" class="form-group row">
<label class="col-sm-2 col-form-label" v-html="key"></label>
<div class="col-sm-10">
<input type="text" v-model="formData.plugins[pluginName][key]" class="form-control">
</div>
</div>
</div>
</div>
</div>
<div v-if="activeTab === 'tasklist'">
<div class="row title">
<div class="col">
<h2>任务列表</h2>
</div>
</div>
<hr>
<div class="row">
<div class="col-lg-6 col-md-12">
<div class="row">
<label class="col-form-label col-md-3">名称筛选</label>
<div class="input-group col-md-9">
<input type="text" class="form-control" v-model="taskNameFilter" placeholder="名称 筛选/搜索">
<div class="input-group-append">
<button type="button" class="btn btn-outline-secondary" @click="clearData('taskNameFilter')"><i class="bi bi-x-lg"></i></button>
</div>
</div>
</div>
</div>
<div class="col-lg-6 col-md-12">
<div class="row">
<label class="col-form-label col-md-3">路径筛选</label>
<div class="input-group col-md-9">
<select class="form-control" v-model="taskDirSelected">
<option v-for="(dir, index) in taskDirs" :value="dir" v-html="dir"></option>
</select>
<div class="input-group-append">
<button type="button" class="btn btn-outline-secondary" @click="clearData('taskDirSelected')"><i class="bi bi-x-lg"></i></button>
</div>
</div>
</div>
</div>
</div>
<div v-for="(task, index) in formData.tasklist" :key="index" class="task mb-3">
<template v-if="(taskDirSelected == '' || getParentDirectory(task.savepath) == taskDirSelected) && task.taskname.includes(taskNameFilter)">
<hr>
<div class="form-group row" style="align-items:center">
<div class="col pl-0" data-toggle="collapse" :data-target="'#collapse_'+index" aria-expanded="true" :aria-controls="'collapse_'+index">
<div class="btn btn-block text-left">
<i class="bi bi-caret-right-fill"></i> #<span v-html="`${index+1}: ${task.taskname}`"></span>
</div>
</div>
<div class="col-auto">
<button class="btn btn-warning" v-if="task.shareurl_ban" :title="task.shareurl_ban" disabled><i class="bi bi-exclamation-triangle-fill"></i></button>
<button type="button" class="btn btn-outline-primary" @click="runScriptNow(index)" title="运行此任务" v-else><i class="bi bi-play-fill"></i></button>
<button type="button" class="btn btn-outline-danger" @click="removeTask(index)" title="删除此任务"><i class="bi bi-trash3-fill"></i></button>
</div>
</div>
<div class="collapse ml-3" :id="'collapse_'+index">
<div class="alert alert-warning" role="alert" v-if="task.shareurl_ban" v-html="task.shareurl_ban"></div>
<div class="form-group row">
<label class="col-sm-2 col-form-label">任务名称</label>
<div class="col-sm-10">
<div class="input-group">
<input type="text" name="taskname[]" class="form-control" v-model="task.taskname" placeholder="必填" @focus="smart_param.showSuggestions=true;focusTaskname(index, task)" @input="changeTaskname(index, task)">
<div class="dropdown-menu show task-suggestions" v-if="smart_param.showSuggestions && smart_param.taskSuggestions.length && smart_param.index === index">
<div class="text-muted text-center" style="font-size: small;">以下资源来自第三方,网络公开搜集,请自行辨识,如有侵权请联系夸克官方</div>
<div v-for="suggestion in smart_param.taskSuggestions" :key="suggestion.taskname" class="dropdown-item" @click.prevent="selectSuggestion(task, suggestion)" style="cursor: pointer;">
<span v-html="suggestion.verify ? '✅': '❔'"></span> {{ suggestion.taskname }}
<small class="text-muted">
<a :href="suggestion.shareurl" target="_blank" @click.stop>{{ suggestion.shareurl }}</a>
</small>
</div>
</div>
<div class="input-group-append" title="深度搜索">
<button class="btn btn-primary" type="button" @click="searchSuggestions(index, task.taskname)">
<i v-if="smart_param.isSearching && smart_param.index === index" class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></i>
<i v-else class="bi bi-search-heart"></i>
</button>
<div class="input-group-text" title="谷歌搜索">
<a target="_blank" :href="`https://www.google.com/search?q=%22pan.quark.cn/s%22+${task.taskname}`"><i class="bi bi-google"></i></a>
</div>
</div>
</div>
</div>
</div>
<div class="form-group row">
<label class="col-sm-2 col-form-label">分享链接</label>
<div class="col-sm-10">
<div class="input-group">
<input type="text" name="shareurl[]" class="form-control" v-model="task.shareurl" placeholder="必填" @blur="changeShareurl(task)">
<div class="input-group-append" v-if="task.shareurl">
<div class="input-group-text">
<a target="_blank" :href="task.shareurl"><i class="bi bi-box-arrow-up-right"></i></a>
</div>
</div>
</div>
</div>
</div>
<div class="form-group row">
<label class="col-sm-2 col-form-label">保存路径</label>
<div class="col-sm-10">
<div class="input-group">
<input type="text" name="savepath[]" class="form-control" v-model="task.savepath" placeholder="必填" @focus="focusTaskname(index, task)">
<div class="input-group-append">
<button class="btn btn-secondary" type="button" v-if="smart_param.savepath && smart_param.index == index && task.savepath != smart_param.origin_savepath" @click="task.savepath = smart_param.origin_savepath"><i class="
bi bi-reply"></i></button>
<button class="btn btn-outline-secondary dropdown-toggle" type="button" @click="getSavepathDirs(task.savepath)" data-toggle="dropdown" aria-expanded="false">选择</button>
<div class="dropdown-menu" style="max-height: 300px; min-width: 200px; overflow-y: auto;">
<a class="dropdown-item" @click.stop.prevent="selectSavepath(index,getParentDirectory(task.savepath),'..')" href="#">..</a>
<span v-if="!savepaths.some(item => item.dir)" class="dropdown-item disabled">无子目录</span>
<a v-for="(item, key) in savepaths" :class="{'disabled': item.fid === 0 || !item.dir}" class="dropdown-item" @click.stop.prevent="selectSavepath(index,item.fid,item.file_name)" href="#">
<i class="bi" :class="item.dir ? 'bi-folder2' : 'bi-file-earmark'"></i> {{ item.file_name }}
<i class="bi bi-trash3-fill text-danger" @click.stop.prevent="deleteFile(item.fid,item.file_name,item.dir)" style="position: absolute; right: 10px; pointer-events: auto;"></i>
</a>
</div>
</div>
</div>
</div>
</div>
<div class="form-group row">
<label class="col-sm-2 col-form-label">命名方式</label>
<div class="col-sm-10">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text">{{ task.use_sequence_naming ? '顺序命名' : '正则命名' }}</span>
</div>
<input type="text" name="pattern[]" class="form-control" v-model="task.pattern" placeholder="匹配表达式 (E{} 或 S01E{} 表示顺序命名模式)" list="magicRegex" @input="detectNamingMode(task)">
<input type="text" name="replace[]" class="form-control" v-model="task.replace" placeholder="替换表达式 (正则模式时有效)" @input="detectNamingMode(task)">
<div class="input-group-append">
<div class="input-group-text">
<input type="checkbox" title="忽略后缀" v-model="task.ignore_extension">&nbsp;忽略后缀
</div>
</div>
</div>
<datalist id="magicRegex">
<option v-for="(value, key) in formData.magic_regex" :key="key" :value="`${key}`" v-html="`${value.pattern.replace('<', '<\u200B')} → ${value.replace}`"></option>
</datalist>
</div>
</div>
<div class="form-group row">
<label class="col-sm-2 col-form-label">过滤规则</label>
<div class="col-sm-10">
<div class="input-group">
<input type="text" name="filterwords[]" class="form-control" v-model="task.filterwords" placeholder="可选,输入过滤词汇,用逗号分隔,例如:纯享,加更,超前企划,名称包含过滤词汇的项目不会被转存">
</div>
</div>
</div>
<div class="form-group row">
<label class="col-sm-2 col-form-label">文件开始</label>
<div class="col-sm-10">
<div class="input-group">
<input type="text" class="form-control" placeholder="可选" name="startfid[]" v-model="task.startfid">
<div class="input-group-append" v-if="task.shareurl">
<button class="btn btn-outline-secondary" type="button" @click="showShareFiles(index)">选择</button>
</div>
</div>
</div>
</div>
<div class="form-group row">
<label class="col-sm-2 col-form-label">更子目录</label>
<div class="col-sm-10">
<input type="text" name="update_subdir[]" class="form-control" v-model="task.update_subdir" placeholder="可选,需更新子目录的正则式,多项以|分割,如 4k|1080p ,注意!深层嵌套目录慎用 .* " title="注意!深层嵌套目录逐级索引,工作强度会非常大,慎用!">
</div>
</div>
<div class="form-group row">
<label class="col-sm-2 col-form-label">截止日期</label>
<div class="col-sm-10">
<input type="date" name="enddate[]" class="form-control" v-model="task.enddate" placeholder="可选">
</div>
</div>
<div class="form-group row">
<label class="col-sm-2 col-form-label">运行星期</label>
<div class="col-sm-10 col-form-label">
<div class="form-check form-check-inline" title="也可用作任务总开关">
<input class="form-check-input" type="checkbox" :checked="task.runweek.length === 7" @change="toggleAllWeekdays(task)" :indeterminate.prop="task.runweek.length > 0 && task.runweek.length < 7">
<label class="form-check-label">全选</label>
</div>
<div class="form-check form-check-inline" v-for="(day, index) in weekdays" :key="index">
<input class="form-check-input" type="checkbox" v-model="task.runweek" :value="index+1">
<label class="form-check-label" v-html="day"></label>
</div>
</div>
</div>
<div class="form-group row" v-if="Object.keys(getAvailablePlugins(formData.plugins)).length">
<label class="col-sm-2 col-form-label">插件选项</label>
<div class="col-sm-10">
<v-jsoneditor v-model="task.addition" :options="{mode:'tree'}" :plus="false" height="180px"></v-jsoneditor>
</div>
</div>
</div>
</template>
</div>
<div class="row mt-5">
<div class="col-sm-12 text-center">
<button type="button" class="btn btn-primary" @click="addTask()"><i class="bi bi-plus"></i> 增加任务</button>
</div>
</div>
</div>
<div class="bottom-buttons">
<button class="btn btn-success" title="保存 CTRL+S"><i class="bi bi-floppy2-fill"></i></button>
<button type="button" class="btn btn-primary" title="运行 CTRL+R" @click="runScriptNow()"><i class="bi bi-play-fill"></i></button>
<button type="button" class="btn btn-info" @click="scrollToX(0)" @dblclick="scrollToX()" data-toggle="tooltip" data-placement="top" title="单击回顶,双击到底"><i class="bi bi-chevron-bar-up"></i></button>
</div>
</form>
</main>
</div>
<!-- 模态框 运行日志 -->
<div class="modal" tabindex="-1" id="logModal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><b>运行日志</b>
<div v-if="modalLoading" class="spinner-border spinner-border-sm m-1" role="status"></div>
</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<pre v-html="run_log"></pre>
</div>
</div>
</div>
</div>
<!-- 模态框 分享文件列表 -->
<div class="modal" tabindex="-1" id="shareDetailModal">
<div class="modal-dialog modal-xl">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><b>分享文件列表</b>
<div v-if="modalLoading" class="spinner-border spinner-border-sm m-1" role="status"></div>
</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="alert alert-warning" v-if="shareFiles.error" v-html="shareFiles.error"></div>
<table class="table table-hover table-sm" v-else-if="!modalLoading" title="请选择转存起始文件,将只转存修改日期>选中文件的文件">
<thead>
<tr>
<!-- <th scope="col">fid</th> -->
<th scope="col">文件名</th>
<th scope="col">大小</th>
<th scope="col">修改日期 ↓</th>
</tr>
</thead>
<tbody>
<tr>
<td colspan="4"><i class="bi bi-folder-plus"></i> 后续更新的文件...</td>
</tr>
<tr v-for="(file, key) in shareFiles" :key="key" @click="selectStartFid(file.fid)" style="cursor: pointer;">
<!-- <td>{{file.fid}}</td> -->
<td><i class="bi" :class="file.dir ? 'bi-folder2' : 'bi-file-earmark'"></i> {{file.file_name}}</td>
<td>{{file.size | size}}</td>
<td>{{file.last_update_at | ts2date}}</td>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<script>
var app = new Vue({
el: '#app',
data: {
version: "[[ version ]]",
versionTips: "",
plugin_flags: "[[ plugin_flags ]]",
weekdays: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
formData: {
cookie: [],
push_config: {},
media_servers: {},
tasklist: [],
magic_regex: {}
},
newTask: {
taskname: "",
shareurl: "",
savepath: "/",
pattern: "",
replace: "",
enddate: "",
addition: {},
ignore_extension: false,
filterwords: "",
sequence_naming: "",
use_sequence_naming: false,
runweek: [1, 2, 3, 4, 5, 6, 7]
},
run_log: "",
taskDirs: [""],
taskDirSelected: "",
taskNameFilter: "",
savepaths: [],
modalLoading: false,
shareFiles: [],
forceTaskIndex: null,
smart_param: {
index: null,
savepath: "",
origin_savepath: "",
taskSuggestions: [],
showSuggestions: false,
lastSuggestionsTime: 0,
isSearching: false,
},
activeTab: 'tasklist',
},
filters: {
ts2date: function (value) {
const date = new Date(value);
return `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()} ${date.getHours()}:${date.getMinutes().toString().padStart(2, '0')}`;
},
size: function (value) {
if (!value) return "0B";
const unitArr = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
const srcsize = parseFloat(value);
const index = srcsize ? Math.floor(Math.log(srcsize) / Math.log(1024)) : 0;
const size = (srcsize / Math.pow(1024, index)).toFixed(2).replace(/\.?0+$/, "");
return size + unitArr[index];
}
},
mounted() {
this.fetchData();
this.checkNewVersion();
$('[data-toggle="tooltip"]').tooltip();
document.addEventListener('keydown', this.handleKeyDown);
document.addEventListener('click', (e) => {
if (!e.target.closest('.input-group')) {
this.smart_param.showSuggestions = false;
}
});
// 初始化时检查所有任务的命名模式
setTimeout(() => {
if (this.formData.tasklist && this.formData.tasklist.length > 0) {
this.formData.tasklist.forEach(task => {
// 检查现有的顺序命名设置
if (task.use_sequence_naming && task.sequence_naming) {
// 已经设置过顺序命名的,将顺序命名模式转换为匹配表达式
if (!task.pattern || task._pattern_backup) {
task.pattern = task.sequence_naming;
}
} else {
// 检测是否包含顺序命名模式
this.detectNamingMode(task);
}
});
}
}, 500);
},
methods: {
changeTab(tab) {
this.activeTab = tab;
if (window.innerWidth <= 768) {
$('#sidebarMenu').collapse('toggle')
}
},
checkNewVersion() {
this.versionTips = this.version;
axios.get('https://api.github.com/repos/Cp0204/quark-auto-save/tags')
.then(response => {
latestVersion = response.data[0].name;
console.log(`检查版本:当前 ${this.version} 最新 ${latestVersion}`);
if (latestVersion != this.version) {
this.versionTips += ` <sup><span class="badge badge-pill badge-danger">${latestVersion}</span></sup>`;
}
})
.catch(error => {
console.error('Error:', error);
});
},
fetchData() {
axios.get('/data')
.then(response => {
// cookie兼容
if (typeof response.data.cookie === 'string')
response.data.cookie = [response.data.cookie];
// 添加星期预设
response.data.tasklist = response.data.tasklist.map(task => {
if (!task.hasOwnProperty('runweek')) {
task.runweek = [1, 2, 3, 4, 5, 6, 7];
}
return task;
});
// 获取所有任务父目录
response.data.tasklist.forEach(item => {
parentDir = this.getParentDirectory(item.savepath)
if (!this.taskDirs.includes(parentDir))
this.taskDirs.push(parentDir);
});
this.newTask.addition = response.data.task_plugins_config;
this.formData = response.data;
})
.catch(error => {
console.error('Error fetching data:', error);
});
},
handleKeyDown(event) {
if (event.ctrlKey || event.metaKey) {
if (event.keyCode === 83 || event.key === 's') {
event.preventDefault();
this.saveConfig();
} else if (event.keyCode === 82 || event.key === 'r') {
event.preventDefault();
this.runScriptNow();
}
}
},
saveConfig() {
// 保存前处理每个任务的命名模式
if (this.formData.tasklist && this.formData.tasklist.length > 0) {
this.formData.tasklist.forEach(task => {
// 如果是顺序命名模式确保sequence_naming字段已正确设置
if (task.use_sequence_naming && task.pattern && task.pattern.includes('E{}')) {
task.sequence_naming = task.pattern;
}
});
}
axios.post('/update', this.formData)
.then(response => {
alert(response.data);
console.log('Config saved successfully:', response.data);
})
.catch(error => {
console.error('Error saving config:', error);
});
},
addCookie() {
this.formData.cookie.push("");
},
removeCookie(index) {
if (this.formData.cookie[index] == "" || confirm("确认删除吗?"))
this.formData.cookie.splice(index, 1);
},
addPush() {
key = prompt("增加的键名", "");
if (key != "" && key != null)
this.$set(this.formData.push_config, key, "");
},
removePush(key) {
if (confirm("确认删除吗?"))
this.$delete(this.formData.push_config, key);
},
addTask() {
newTask = { ...this.newTask }
newTask.taskname = this.taskNameFilter;
if (this.formData.tasklist.length > 0) {
lastTask = this.formData.tasklist[this.formData.tasklist.length - 1];
if (this.taskDirSelected) {
newTask.savepath = this.taskDirSelected + '/TASKNAME';
} else {
if (newTask.taskname) {
newTask.savepath = lastTask.savepath.replace(lastTask.taskname, newTask.taskname);
} else {
newTask.savepath = lastTask.taskname ? lastTask.savepath.replace(lastTask.taskname, 'TASKNAME') : lastTask.savepath;
}
}
}
// 初始化新任务的命名模式相关字段
if (newTask.taskname) {
// 默认使用正则命名模式
newTask.pattern = ".*"; // 默认匹配所有文件
newTask.replace = ""; // 默认保持原文件名
newTask.use_sequence_naming = false;
newTask.sequence_naming = "";
}
this.formData.tasklist.push(newTask);
// 滚到最下
setTimeout(() => {
$('#collapse_' + (this.formData.tasklist.length - 1)).collapse('show').on('shown.bs.collapse', () => {
this.scrollToX();
});
}, 1);
},
focusTaskname(index, task) {
this.smart_param.index = index
this.smart_param.origin_savepath = task.savepath
regex = new RegExp(`/${task.taskname.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(/|$)`)
if (task.savepath.includes('TASKNAME')) {
this.smart_param.savepath = task.savepath;
} else if (task.savepath.match(regex)) {
this.smart_param.savepath = task.savepath.replace(task.taskname, 'TASKNAME');
} else {
this.smart_param.savepath = undefined;
}
},
changeTaskname(index, task) {
this.searchSuggestions(index, task.taskname, 500);
if (this.smart_param.savepath)
task.savepath = this.smart_param.savepath.replace('TASKNAME', task.taskname);
},
removeTask(index) {
if (confirm("确认删除任务 [#" + (index + 1) + ": " + this.formData.tasklist[index].taskname + "] 吗?"))
this.formData.tasklist.splice(index, 1);
},
changeShareurl(task) {
if (!task.shareurl)
return;
this.$set(task, "shareurl_ban", undefined);
// 从URL中提取任务名
try {
const matches = decodeURIComponent(task.shareurl).match(/\/(\w{32})-([^\/]+)$/);
if (matches) {
task.taskname = task.taskname == "" ? matches[2] : task.taskname;
task.savepath = task.savepath.replace(/TASKNAME/g, matches[2]);
}
} catch (e) {
console.error("Error decodeURIComponent:", e);
}
// 从分享中提取任务名
axios.get('/get_share_detail', { params: { shareurl: task.shareurl } })
.then(response => {
if (response.data.error) {
if (response.data.error.includes("提取码")) {
const passcode = prompt("检查失败[" + response.data.error + "],请输入提取码:");
if (passcode != null) {
task.shareurl = task.shareurl.replace(/pan.quark.cn\/s\/(\w+)(\?pwd=\w*)*/, `pan.quark.cn/s/$1?pwd=${passcode}`);
this.changeShareurl(task);
return;
}
}
this.$set(task, "shareurl_ban", response.data.error);
} else {
task.taskname = task.taskname == "" ? response.data.share.title : task.taskname;
task.savepath = task.savepath.replace(/TASKNAME/g, response.data.share.title);
this.$set(task, "shareurl_ban", undefined);
}
})
.catch(error => {
console.error('Error get_share_detail:', error);
});
},
clearData(target) {
this[target] = "";
},
selectSavepath(index, fid, name) {
const savepath = name == ".." ? this.getParentDirectory(this.formData.tasklist[index].savepath) : `/${this.formData.tasklist[index].savepath}/${name}`.replace(/\/{2,}/g, '/')
Vue.set(this.formData.tasklist[index], 'savepath', savepath);
this.getSavepathDirs(fid);
},
getSavepathDirs(fid = 0) {
if (fid.includes('/')) {
params = { path: fid }
} else {
params = { fid: fid }
}
this.savepaths = [{ fid: 0, dir: true, file_name: "加载中..." }]
axios.get('/get_savepath', { params: params })
.then(response => {
this.savepaths = response.data
})
.catch(error => {
console.error('Error get_savepath:', error);
});
},
deleteFile(fid, fname, isDir) {
if (fid != "" && confirm(`确认删除${isDir ? '目录' : '文件'} [${fname}] 吗?`))
axios.post('/delete_file', { fid: fid })
.then(response => {
if (response.data.code == 0) {
this.savepaths = this.savepaths.filter(item => item.fid != fid);
} else {
alert('删除失败:' + response.data.message);
}
})
.catch(error => {
console.error('Error delete_file:', error);
});
},
selectStartFid(fid) {
Vue.set(this.formData.tasklist[this.forceTaskIndex], 'startfid', fid);
$('#shareDetailModal').modal('toggle')
},
showShareFiles(index) {
this.shareFiles = []
$('#shareDetailModal').modal('toggle')
this.modalLoading = true
axios.get('/get_share_detail', { params: { shareurl: this.formData.tasklist[index].shareurl } })
.then(response => {
this.forceTaskIndex = index
this.shareFiles = response.data.list;
this.modalLoading = false
})
.catch(error => {
console.error('Error get_share_detail:', error);
});
},
runScriptNow(task_index = "") {
$('#logModal').modal('toggle')
this.modalLoading = true
this.run_log = ''
const source = new EventSource(`/run_script_now?task_index=${task_index}`);
source.onmessage = (event) => {
if (event.data == "[DONE]") {
this.modalLoading = false
source.close();
// 运行后刷新数据
this.fetchData();
} else {
this.run_log += event.data + '\n';
// 在更新 run_log 后将滚动条滚动到底部
this.$nextTick(() => {
const modalBody = document.querySelector('.modal-body');
modalBody.scrollTop = modalBody.scrollHeight;
});
}
};
source.onerror = (error) => {
this.modalLoading = false
console.error('Error:', error);
source.close();
};
},
getParentDirectory(path) {
parentDir = path.substring(0, path.lastIndexOf('/'))
if (parentDir == "")
parentDir = "/"
return parentDir;
},
scrollToX(top = undefined) {
if (top == undefined)
top = document.documentElement.scrollHeight
window.scrollTo({
top: top,
behavior: "smooth"
});
},
getAvailablePlugins(plugins) {
availablePlugins = {};
const pluginsFlagsArray = this.plugin_flags.split(',');
for (const pluginName in plugins) {
if (!pluginsFlagsArray.includes(`-${pluginName}`)) {
availablePlugins[pluginName] = plugins[pluginName];
}
}
return availablePlugins;
},
toggleAllWeekdays(task) {
if (task.runweek.length === 7) {
task.runweek = [];
} else {
task.runweek = [1, 2, 3, 4, 5, 6, 7];
}
},
searchSuggestions(index, taskname, limit_msec = 0) {
if (taskname.length == 0)
return
if (limit_msec > 0) {
const now = Date.now();
if (now - this.smart_param.lastSuggestionsTime < limit_msec)
return;
this.smart_param.lastSuggestionsTime = now;
}
this.smart_param.isSearching = true
this.smart_param.index = index;
axios.get('/task_suggestions', {
params: {
q: taskname,
d: limit_msec == 0 ? 1 : 0
}
}).then(response => {
this.smart_param.taskSuggestions = response.data;
this.smart_param.showSuggestions = true;
}).catch(error => {
console.error('Error fetching suggestions:', error);
}).finally(() => {
this.smart_param.isSearching = false;
});
},
selectSuggestion(task, suggestion) {
task.taskname = suggestion.taskname;
task.shareurl = suggestion.shareurl;
this.changeShareurl(task);
this.smart_param.showSuggestions = false;
},
detectNamingMode(task) {
// 检测是否为顺序命名模式
const sequencePatterns = ['E{}', 'EP{}', 'S\\d+E{}', '第{}集', '第{}话', '第{}期'];
let isSequenceNaming = false;
// 保存当前值以支持撤销操作
const currentValue = task.pattern;
if (task.pattern) {
// 检查是否包含任何顺序命名模式
isSequenceNaming = sequencePatterns.some(pattern => {
const regexPattern = pattern.replace('{}', '\\{\\}');
return new RegExp(regexPattern).test(task.pattern);
});
// 或者用户直接输入包含{}的格式,且替换表达式为空
if (!isSequenceNaming && task.pattern.includes('{}') && (!task.replace || task.replace === '')) {
isSequenceNaming = true;
}
}
// 处理模式切换
if (isSequenceNaming) {
// 如果当前不是顺序命名模式,则保存现有的正则表达式
if (!task.use_sequence_naming) {
task._pattern_backup = task.pattern;
task._replace_backup = task.replace;
task.use_sequence_naming = true;
}
// 设置序列命名模式
task.sequence_naming = task.pattern;
} else {
// 如果当前是顺序命名模式,但现在检测不到顺序命名模式
if (task.use_sequence_naming) {
// 如果用户正在删除内容(当前值为空或比上一次更短)
if (!currentValue || (task._lastPatternValue && currentValue.length < task._lastPatternValue.length)) {
// 保持当前编辑状态,不切换模式
task.sequence_naming = currentValue;
// 只有当完全删除后才切换回正则模式
if (!currentValue) {
task.use_sequence_naming = false;
if (task._pattern_backup) {
task.pattern = "";
task.replace = task._replace_backup || "";
}
task.sequence_naming = null;
}
} else if (task._pattern_backup && !task.pattern.includes('{}')) {
// 正常切换回正则命名模式(非删除操作)
task.use_sequence_naming = false;
task.pattern = task._pattern_backup;
task.replace = task._replace_backup;
task._sequence_backup = task.sequence_naming;
task.sequence_naming = null;
} else if (!task._pattern_backup && !task.pattern.includes('{}')) {
// 没有备份,但需要切换回正则模式
task.use_sequence_naming = false;
task.sequence_naming = null;
}
}
}
// 保存当前值,用于下次比较
task._lastPatternValue = currentValue;
// 强制Vue更新视图
this.$forceUpdate();
},
}
});
</script>
</body>
</html>