MissAV-Download/docker/html/home.html
2025-11-07 15:23:04 +08:00

640 lines
24 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>
<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>
</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.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 (item) {
var taskItem = $(`.task-item[data-id="${item.task_id}"]`);
if (taskItem.length > 0) {
var progressPercent = Math.round(item.progress * 100) + '%';
var statusText = item.status === 'completed' ? '已完成' : '进行中';
var statusClass = item.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);
const token = localStorage.getItem('Authorization');
const loadingIndex = layer.load(2, {
shade: [0.1, '#000']
});
fetch(`/api/check/${encodeURIComponent(url)}`, {
method: 'GET',
headers: {
'Authorization': token,
'Content-Type': 'application/json'
}
})
.then(response => {
if (!response.ok) {
throw new Error('检查URL失败');
}
return response.json();
})
.then(data => {
layer.close(loadingIndex);
if (data.code !== 200 || !data.data) {
layer.msg('接口返回异常', { icon: 2 });
return;
}
const { serial_number, title, url: urlList } = data.data;
// 检测设备类型
const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
// 动态生成URL列表HTML - 响应式布局
const urlHtml = urlList.map((u, index) => {
if (isMobile) {
// 手机端布局 - 垂直排列
return `
<li class="layui-col-xs12" style="padding: 10px 0; border-bottom: 1px solid #f6f6f6;">
<div style="word-break: break-all; font-size: 13px; color: #666; margin-bottom: 8px;">
${u}
</div>
<button class="layui-btn layui-btn-sm layui-btn-normal select-url"
data-serial="${serial_number}"
data-url="${u}"
style="width: 100%;">
选择
</button>
</li>`;
} else {
// 电脑端布局 - 水平排列
return `
<li class="layui-row" style="padding: 8px 0; border-bottom: 1px solid #f6f6f6; margin: 0;">
<div class="layui-col-xs10" style="word-break: break-all; font-size: 13px; color: #666; line-height: 32px;">
${u}
</div>
<div class="layui-col-xs2" style="text-align: right;">
<button class="layui-btn layui-btn-sm layui-btn-normal select-url"
data-serial="${serial_number}"
data-url="${u}">
选择
</button>
</div>
</li>`;
}
}).join('');
// 响应式弹窗设置
const popupSettings = isMobile ? {
area: ['90vw', '70vh'],
content: `
<div style="padding: 15px;">
<div class="layui-form-item" style="margin-bottom: 15px;">
<label class="layui-form-label" style="width: 80px; padding: 9px 5px;">番号:</label>
<div class="layui-input-block">
<input type="text" class="layui-input" value="${serial_number || ''}" readonly
style="background-color: #f8f8f8; color: #666; font-size: 14px;">
</div>
</div>
<div class="layui-form-item" style="margin-bottom: 15px;">
<label class="layui-form-label" style="width: 80px; padding: 9px 5px;">标题:</label>
<div class="layui-input-block">
<input type="text" class="layui-input" value="${title || ''}" readonly
style="background-color: #f8f8f8; color: #666; font-size: 14px;">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label" style="width: 80px; padding: 9px 5px;">URL列表</label>
<div class="layui-input-block">
<div style="max-height: 40vh; overflow-y: auto; border: 1px solid #e6e6e6; border-radius: 4px; padding: 10px;">
<ul style="list-style: none; padding: 0; margin: 0;">${urlHtml || '<li style="padding: 10px; text-align: center; color: #999;">无URL</li>'}</ul>
</div>
</div>
</div>
</div>
`
} : {
area: ['580px', '420px'],
content: `
<div style="padding: 15px;">
<div class="layui-form-item">
<label class="layui-form-label" style="width: 100px;">番号:</label>
<div class="layui-input-block">
<input type="text" class="layui-input" value="${serial_number || ''}" readonly
style="background-color: #f8f8f8; color: #666;">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label" style="width: 100px;">标题:</label>
<div class="layui-input-block">
<input type="text" class="layui-input" value="${title || ''}" readonly
style="background-color: #f8f8f8; color: #666;">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label" style="width: 100px;">URL列表</label>
<div class="layui-input-block">
<div style="max-height: 200px; overflow-y: auto; border: 1px solid #e6e6e6; border-radius: 2px; padding: 0 10px;">
<ul style="list-style: none; padding: 0; margin: 0;">${urlHtml || '<li style="padding: 10px; text-align: center; color: #999;">无URL</li>'}</ul>
</div>
</div>
</div>
</div>
`
};
// 打开弹窗
const popupIndex = layer.open({
type: 1,
title: '链接详情',
...popupSettings,
btn: ['关闭'],
success: function (layero) {
// 为选择按钮绑定点击事件
$(layero).find('.select-url').on('click', function () {
const serial = $(this).data('serial');
const selectedUrl = $(this).data('url');
// 响应式提示框
const msgSettings = isMobile ? {
area: ['80vw', 'auto'],
time: 4000
} : {
area: ['400px', 'auto'],
time: 3000
};
const downloadLoading = layer.msg('正在提交下载任务...', {icon: 16, time: 0});
fetch('/api/download', {
method: 'POST',
headers: {
'Authorization': token,
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: serial,
url: selectedUrl
})
})
.then(response => {
if (!response.ok) {
throw new Error('下载任务提交失败');
}
return response.json();
})
.then(data => {
layer.close(downloadLoading);
if (data.code === 200) {
layer.msg('下载任务提交成功', {icon: 1});
// 关闭所有弹出层
layer.closeAll();
// 重新加载任务列表
initTaskList();
} else {
layer.msg('下载任务提交失败: ' + data.msg, {icon: 2});
}
})
.catch(error => {
layer.close(downloadLoading);
layer.msg('下载任务提交失败: ' + error.message, {icon: 2});
});
});
},
yes: function(index) {
layer.close(index);
}
});
})
.catch(error => {
layer.close(loadingIndex);
layer.msg('添加任务失败: ' + error.message, { icon: 2, time: 2000 });
});
});
});
initTaskList();
});
</script>
</body>
</html>