添加页面

This commit is contained in:
Krcia 2025-11-07 13:16:09 +08:00
parent 00ff77f7d7
commit 76d8726ee5
13 changed files with 1079 additions and 23 deletions

1
.gitignore vendored
View File

@ -160,3 +160,4 @@ cython_debug/
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
/.idea/

16
api.py
View File

@ -93,8 +93,7 @@ def login():
@app.route('/api/check/<path:url>')
@token_required
def check_url(url):
status = is_from_missav(url)
if (status):
try:
result = asyncio.run(crawl_missav(
url
))
@ -103,7 +102,7 @@ def check_url(url):
'code': 200,
'dat': result
}), 200
else:
except:
return jsonify({
'msg': '不是来自missav的链接',
'code': 500
@ -111,7 +110,7 @@ def check_url(url):
@app.route('/api/download', methods=['POST'])
# @token_required
@token_required
def download():
data = request.get_json()
@ -163,15 +162,6 @@ def progress(task_id):
}), 200
def is_from_missav(url):
try:
parsed = urlparse(url)
hostname = parsed.netloc.lower()
return hostname == 'missav.ws' or hostname.endswith('.missav.ws')
except:
return False
if __name__ == '__main__':
# 检查环境变量是否设置
if not USERNAME or not PASSWORD:

480
docker/html/home.html Normal file
View File

@ -0,0 +1,480 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>响应式任务列表</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/layui@2.9.6/dist/css/layui.min.css">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
background-color: #f0f2f5;
color: #333;
line-height: 1.6;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
.task-manager {
background: #fff;
border-radius: 10px;
box-shadow: 0 2px 12px rgba(0, 0, 150, 0.1);
overflow: hidden;
}
.task-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px;
background: linear-gradient(135deg, #1e6fde 0%, #0d5bc2 100%);
color: white;
}
.task-title {
font-size: 20px;
font-weight: bold;
}
.add-btn {
background: rgba(255, 255, 255, 0.2);
color: white;
border-radius: 50%;
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s;
border: none;
}
.add-btn:hover {
background: rgba(255, 255, 255, 0.3);
transform: scale(1.05);
}
.task-list {
padding: 10px 0;
}
.task-item {
display: flex;
align-items: center;
padding: 15px 20px;
border-bottom: 1px solid #f0f2ff;
transition: background 0.3s;
}
.task-item:hover {
background: #fafbff;
}
.task-item:last-child {
border-bottom: none;
}
.file-info {
flex: 2;
display: flex;
align-items: center;
}
.file-icon {
width: 36px;
height: 36px;
border-radius: 6px;
background: #e6f7ff;
display: flex;
align-items: center;
justify-content: center;
margin-right: 12px;
color: #1e6fde;
}
.file-name {
color: #333;
font-weight: 500;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.progress-container {
flex: 3;
padding: 0 15px;
}
.progress-info {
flex: 1;
text-align: center;
color: #1e6fde;
font-weight: 500;
min-width: 60px;
}
.task-status {
flex: 1;
text-align: center;
padding: 6px 12px;
border-radius: 12px;
font-size: 12px;
font-weight: bold;
min-width: 70px;
}
.status-completed {
background: #e6f7ff;
color: #1890ff;
}
.status-processing {
background: #f0f6ff;
color: #1e6fde;
}
.empty-state {
text-align: center;
padding: 40px 20px;
color: #999;
}
.empty-state i {
font-size: 50px;
margin-bottom: 15px;
color: #d9d9d9;
}
.loading-state {
text-align: center;
padding: 30px 20px;
color: #1e6fde;
}
.error-state {
text-align: center;
padding: 40px 20px;
color: #ff4d4f;
}
.error-state i {
font-size: 50px;
margin-bottom: 15px;
color: #ff4d4f;
}
.last-update {
text-align: right;
padding: 10px 20px;
font-size: 12px;
color: #999;
border-top: 1px solid #f0f2ff;
}
/* 响应式设计 */
@media (max-width: 768px) {
.task-item {
flex-direction: column;
align-items: flex-start;
padding: 15px;
}
.file-info {
width: 100%;
margin-bottom: 10px;
}
.progress-container {
width: 100%;
padding: 0;
margin-bottom: 10px;
}
.progress-info {
text-align: left;
margin-bottom: 10px;
}
.task-status {
align-self: flex-end;
margin-top: 5px;
}
.task-header {
padding: 15px;
}
.task-title {
font-size: 18px;
}
.last-update {
text-align: center;
}
}
/* 进度条自定义样式 */
.layui-progress {
margin: 5px 0;
}
.layui-progress-bar {
background: linear-gradient(90deg, #1e6fde, #4a90e2);
border-radius: 1px;
transition: width 0.5s ease-in-out;
}
/* 更新动画 */
.progress-updating {
animation: pulse 1s infinite;
}
@keyframes pulse {
0% {
opacity: 1;
}
50% {
opacity: 0.7;
}
100% {
opacity: 1;
}
}
</style>
</head>
<body>
<div class="container">
<div class="task-manager">
<div class="task-header">
<div class="task-title">任务列表</div>
<button class="add-btn" id="addTask">
<i class="layui-icon">&#xe654;</i>
</button>
</div>
<div class="task-list" id="taskList">
<div class="loading-state">
<i class="layui-icon layui-icon-loading layui-anim layui-anim-rotate"></i>
<div>正在加载任务数据...</div>
</div>
</div>
<div class="last-update" id="lastUpdate">
最后更新: --
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/layui@2.9.6/dist/layui.min.js"></script>
<script>
layui.use(['element', 'jquery'], function () {
var element = layui.element;
var $ = layui.$;
var updateInterval;
var currentTasks = [];
// 从API获取任务数据
function fetchTasks() {
return new Promise((resolve, reject) => {
// 检查token是否存在
if (!localStorage.getItem('Authorization')) {
reject(new Error('Token不存在'));
return;
}
// 发起带token的请求
fetch('/api/all-task', {
method: 'GET',
headers: {
'Authorization': localStorage.getItem('Authorization'),
'Content-Type': 'application/json'
}
})
.then(response => {
if (response.status === 401) {
throw new Error('认证失败');
}
if (!response.ok) {
throw new Error('网络响应不正常');
}
return response.json();
})
.then(data => {
resolve(data);
})
.catch(error => {
console.error('获取任务数据失败:', error);
// 如果是认证错误,已经处理过了,这里处理其他错误
if (error.message !== '认证失败') {
$('#taskList').html(`
<div class="error-state">
<i class="layui-icon">&#xe69c;</i>
<div>获取数据失败: ${error.message}</div>
<button class="layui-btn layui-btn-primary layui-btn-sm" style="margin-top: 10px;" onclick="initTaskList()">重试</button>
</div>
`);
}
resolve([]);
});
});
}
// 渲染任务列表
function renderTaskList(tasks) {
tasks = tasks.data;
var taskHtml = '';
if (tasks.length === 0) {
taskHtml = `
<div class="empty-state">
<i class="layui-icon">&#xe6a5;</i>
<div>暂无任务</div>
</div>
`;
} else {
tasks.forEach(function (task) {
var statusText = task.status === 'completed' ? '已完成' : '进行中';
var statusClass = task.status === 'completed' ? 'status-completed' : 'status-processing';
var progressPercent = Math.round(task.progress * 100) + '%';
taskHtml += `
<div class="task-item" data-id="${task.id}">
<div class="file-info">
<div class="file-name">${task.fileName}</div>
</div>
<div class="progress-container">
<div class="layui-progress">
<div class="layui-progress-bar" lay-percent="${progressPercent}"></div>
</div>
</div>
<div class="progress-info">${progressPercent}</div>
<div class="task-status ${statusClass}">${statusText}</div>
</div>
`;
});
}
$('#taskList').html(taskHtml);
element.render('progress');
}
// 更新任务进度(不重新渲染整个列表)
function updateTaskProgress(tasks) {
tasks = tasks.data;
tasks.forEach(function () {
var taskItem = $(`.task-item[data-id="${task.id}"]`);
if (taskItem.length > 0) {
var progressPercent = Math.round(task.progress * 100) + '%';
var statusText = task.status === 'completed' ? '已完成' : '进行中';
var statusClass = task.status === 'completed' ? 'status-completed' : 'status-processing';
// 更新进度条
var progressBar = taskItem.find('.layui-progress-bar');
progressBar.attr('lay-percent', progressPercent);
// 添加更新动画
progressBar.addClass('progress-updating');
// 使用动画更新进度
setTimeout(function () {
progressBar.css('width', progressPercent);
progressBar.removeClass('progress-updating');
}, 100);
// 更新进度文本
taskItem.find('.progress-info').text(progressPercent);
// 更新状态
var statusElement = taskItem.find('.task-status');
statusElement.text(statusText);
statusElement.removeClass('status-completed status-processing').addClass(statusClass);
}
});
}
// 初始化任务列表
function initTaskList() {
fetchTasks().then(tasks => {
currentTasks = tasks;
renderTaskList(tasks);
// 启动定时更新
startAutoUpdate();
}).catch(error => {
console.error('初始化任务列表失败:', error);
});
}
// 启动自动更新
function startAutoUpdate() {
// 清除现有定时器
if (updateInterval) {
clearInterval(updateInterval);
}
// 每5秒更新一次
updateInterval = setInterval(function () {
fetchTasks().then(tasks => {
currentTasks = tasks;
updateTaskProgress(tasks);
});
}, 5000);
}
// 添加新任务
$('#addTask').on('click', function () {
layer.prompt({
title: '请输入URL',
formType: 0,
}, function (url, index) {
layer.close(index);
// 获取token
const token = localStorage.getItem('Authorization');
// 调用检查接口
fetch(`/api/check/${encodeURIComponent(url)}`, {
method: 'GET',
headers: {
'Authorization': token,
'Content-Type': 'application/json'
}
})
.then(response => {
console.log('响应状态:', response.status);
console.log('响应头:', response.headers);
if (!response.ok) {
throw new Error('检查URL失败');
}
return response.json();
})
.then(data => {
console.log('接口返回数据:', data);
// 重新加载任务列表
// initTaskList();
})
.catch(error => {
console.log('错误信息:', error);
layer.msg('添加任务失败: ' + error.message);
});
});
});
initTaskList();
});
</script>
</body>
</html>

View File

@ -1,10 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>下载器</title>
</head>
<body>
</body>
</html>

183
docker/html/index.html Normal file
View File

@ -0,0 +1,183 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>简洁登录页面</title>
<link rel="stylesheet" href="/layui/css/layui.css">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
background: linear-gradient(135deg, #1E9FFF 0%, #0081FF 100%);
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
padding: 15px;
}
.login-container {
width: 100%;
max-width: 400px;
}
.login-box {
background-color: white;
border-radius: 8px;
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.2);
overflow: hidden;
}
.login-header {
background: linear-gradient(135deg, #1E9FFF 0%, #0081FF 100%);
color: white;
padding: 30px 20px;
text-align: center;
}
.login-header h2 {
margin: 0;
font-size: 24px;
font-weight: 500;
}
.login-body {
padding: 30px 25px;
}
.layui-form-item {
margin-bottom: 20px;
}
.layui-input {
height: 40px;
border-radius: 4px;
}
.layui-btn {
height: 40px;
border-radius: 4px;
background-color: #1E9FFF;
}
.login-footer {
padding: 0 25px 25px;
text-align: center;
}
.login-footer a {
color: #1E9FFF;
text-decoration: none;
}
.login-footer a:hover {
text-decoration: underline;
}
.response-message {
margin-top: 15px;
padding: 10px;
border-radius: 4px;
text-align: center;
display: none;
}
.success {
background-color: #f0f9eb;
color: #67c23a;
border: 1px solid #e1f3d8;
}
.error {
background-color: #fef0f0;
color: #f56c6c;
border: 1px solid #fbc4c4;
}
</style>
</head>
<body>
<div class="login-container">
<div class="login-box">
<div class="login-header">
<h2>Miss下载器</h2>
</div>
<div class="login-body">
<form class="layui-form" id="loginForm">
<div class="layui-form-item">
<div class="layui-input-wrap">
<input type="text" name="username" placeholder="用户名" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-wrap">
<input type="password" name="password"placeholder="密码" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<button class="layui-btn layui-btn-fluid" lay-submit lay-filter="login">登录</button>
</div>
</form>
<div class="response-message" id="responseMessage"></div>
</div>
</div>
</div>
<script src="/layui/layui.js"></script>
<script>
layui.use(['form', 'layer'], function(){
var form = layui.form;
var layer = layui.layer;
var $ = layui.$;
// 表单提交
form.on('submit(login)', function(data){
var field = data.field;
var responseMessage = document.getElementById('responseMessage');
// 显示加载状态
layer.msg('登录中...');
// AJAX请求获取token
$.ajax({
url: '/api/login',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify({
username: field.username,
password: field.password
}),
success: function(response) {
layer.closeAll(); // 关闭加载提示
if (response && response.data.token) {
// 登录成功
responseMessage.className = 'response-message success';
responseMessage.style.display = 'block';
localStorage.setItem('Authorization', response.data.token);
window.location.href = '/home.html';
} else {
// 登录失败
responseMessage.className = 'response-message error';
responseMessage.innerHTML = '登录失败:' + (response.message || '未知错误');
responseMessage.style.display = 'block';
}
},
error: function(xhr, status, error) {
layer.closeAll(); // 关闭加载提示
// 显示错误信息
responseMessage.className = 'response-message error';
if (xhr.status === 401) {
responseMessage.innerHTML = '登录失败:用户名或密码错误';
} else if (xhr.status === 0) {
responseMessage.innerHTML = '网络错误:无法连接到服务器';
} else {
var errorMsg = xhr.responseJSON && xhr.responseJSON.message ?
xhr.responseJSON.message :
'服务器错误 (' + xhr.status + ')';
responseMessage.innerHTML = '登录失败:' + errorMsg;
}
responseMessage.style.display = 'block';
}
});
return false; // 阻止表单跳转
});
});
</script>
</body>
</html>

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 326 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long