quark-auto-save/app/templates/index.html
xiaoQQya 13244e1dcf
登录状态持久化,默认跳转任务列表 (#56)
* chore: 登录状态持久化,默认 31 天,可修改 PERMANENT_SESSION_LIFETIME 进行配置

* chore: 登录默认跳转页面修改为任务列表
2025-02-06 19:11:33 +08:00

785 lines
37 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">正则处理</span>
</div>
<input type="text" name="pattern[]" class="form-control" v-model="task.pattern" placeholder="匹配表达式" list="magicRegex">
<input type="text" name="replace[]" class="form-control" v-model="task.replace" placeholder="替换表达式">
<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" 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,
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];
}
},
watch: {
},
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;
}
});
},
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() {
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;
}
}
}
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;
},
}
});
</script>
</body>
</html>