Nanny7 commited on
Commit
2c8ad05
·
1 Parent(s): 2aa9e85

前端美化

Browse files
Files changed (2) hide show
  1. __pycache__/server.cpython-313.pyc +0 -0
  2. server.py +611 -78
__pycache__/server.cpython-313.pyc CHANGED
Binary files a/__pycache__/server.cpython-313.pyc and b/__pycache__/server.cpython-313.pyc differ
 
server.py CHANGED
@@ -60,66 +60,485 @@ def root():
60
  <head>
61
  <meta charset="UTF-8">
62
  <meta name="viewport" content="width=device-width, initial-scale=1">
63
- <title>番茄小说下载器</title>
 
64
  <style>
65
- body { background: url("https://bing.img.run/rand_uhd.php") no-repeat center center fixed; background-size: cover; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; color: #333; margin: 0; padding: 0; }
66
- .container { background: rgba(255, 255, 255, 0.9); width: 90%; max-width: 800px; margin: 20px auto; padding: 20px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); color: #333; }
67
- textarea, input[type="text"], button { width: 100%; font-size: 16px; margin-top: 10px; padding: 12px; border-radius: 4px; border: 1px solid #ccc; box-sizing: border-box; }
68
- input[type="text"] { background: #fff; color: #333; }
69
- button { background: #0078D7; color: #fff; cursor: pointer; }
70
- button:hover { background: #005A9E; }
71
- progress { width: 100%; margin-top: 10px; }
72
- a.download-link { color: #0078D7; text-decoration: none; display: block; margin-top: 10px; }
73
- @media (max-width: 600px) {
74
- .container { margin: 10px auto; padding: 10px; }
75
- textarea, input[type="text"], button { font-size: 14px; padding: 8px; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
  }
77
- </style>
78
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
  </head>
80
  <body>
81
- <div class="container">
82
- <h1>番茄小说下载器</h1>
83
- <label for=\"book_ids\">小说ID列表(逗号或空格分隔):</label><br/>
84
- <textarea id=\"book_ids\" rows=\"4\" cols=\"50\"></textarea><br/>
85
- <button id="downloadBtn">排队下载</button>
86
- <div id=\"progress\" style=\"display:none;margin-top:10px;\">\n <progress id=\"progBar\" value=\"0\" max=\"100\"></progress>\n <span id=\"progText\">0%</span>\n </div>\n <div id="downloadResult" style="margin-top:10px;"></div>
87
- <div id="queueSection" style="margin-top:20px;">
88
- <h2>下载队列</h2>
89
- <ul id="taskList"></ul>
90
- <button id="refreshTasks" style="margin-top:5px;">刷新队列</button>
91
  </div>
92
- <div id="completedSection" style="margin-top:20px;">
93
- <h2>已下载小说</h2>
94
- <input type="text" id="searchInput" placeholder="搜索已下载小说" style="width:70%; padding:8px; border:none; border-radius:4px;"/> <button id="searchBtn" style="padding:8px; margin-left:5px;">搜索</button>
95
- <ul id="completedList"></ul>
96
- <div id="pagination" style="margin-top:5px;">
97
- <button id="prevPage">上一页</button> <span id="pageInfo">第1/1页</span> <button id="nextPage">下一页</button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
98
  </div>
99
- <button id="refreshCompleted" style="margin-top:5px;">刷新已下载</button>
100
  </div>
101
  <script>
102
- document.getElementById('downloadBtn').addEventListener('click', async function(){ // 动态排队或下载
103
- const book_ids = document.getElementById('book_ids').value;
104
- if (!book_ids) return;
105
- // 根据当前队列决定是否排队
106
- const tasks = await fetch('/tasks').then(res => res.json());
107
- if (tasks.filter(t => t.status !== 'done').length > 0) {
108
- const ids = book_ids.split(/[\s,;]+/).filter(id => id);
109
- ids.forEach(bid => {
110
- fetch(`/enqueue?book_id=${encodeURIComponent(bid)}`)
111
- .then(res => res.json())
112
- .then(() => fetchTasks())
113
- .catch(console.error);
114
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
115
  return;
116
  }
117
- document.getElementById('progress').style.display = 'block';
118
- fetch(`/download?book_ids=${encodeURIComponent(book_ids)}`).then(response=>{
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
119
  const total = response.headers.get('Content-Length');
120
  const reader = response.body.getReader();
121
  let received = 0;
122
  const chunks = [];
 
123
  function read() {
124
  return reader.read().then(({done, value})=>{
125
  if (done) {
@@ -131,36 +550,88 @@ def root():
131
  const match = cd.match(/filename="?(.+)"?/);
132
  if (match) filename = match[1];
133
  }
134
- // 生成下载链接并展示
 
135
  const link = document.createElement('a');
136
  link.href = url;
137
  link.download = filename;
138
- link.innerText = `下载完成:点击此处下载 ${filename}`;
139
- link.style.display = 'block';
140
- link.style.marginTop = '10px';
141
- document.getElementById('downloadResult').appendChild(link);
 
 
 
 
 
 
142
  return;
143
  }
 
144
  chunks.push(value);
145
  received += value.length;
146
  if (total) {
147
  const percent = Math.round(received / total * 100);
148
- document.getElementById('progBar').value = percent;
149
  document.getElementById('progText').innerText = percent + '%';
150
  }
151
  return read();
152
  });
153
  }
154
  read();
155
- });
 
 
 
 
156
  });
157
  // 刷新队列和已下载列表
158
  function fetchTasks() {
159
  fetch('/tasks').then(res => res.json()).then(data => {
160
- const ul = document.getElementById('taskList'); ul.innerHTML = '';
161
- data.filter(task => task.status !== 'done').forEach(task => { const li = document.createElement('li'); li.textContent = `${task.name || task.book_id}: ${task.status}`; ul.appendChild(li); });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
162
  });
163
  }
 
164
  // 分页和搜索设置
165
  let currentPage = 0;
166
  const pageSize = 10;
@@ -169,66 +640,128 @@ def root():
169
  const url = name ? `/search?name=${encodeURIComponent(name)}` : '/search';
170
  fetch(url).then(res => res.json()).then(data => {
171
  renderCompleted(data);
 
 
172
  });
173
  }
174
 
175
  function renderCompleted(data) {
176
- const ul = document.getElementById('completedList');
177
- ul.innerHTML = '';
 
 
 
 
 
 
 
 
178
  const total = data.length;
179
  const totalPages = Math.ceil(total / pageSize) || 1;
180
  if (currentPage >= totalPages) currentPage = totalPages - 1;
 
 
181
  const slice = data.slice(currentPage * pageSize, (currentPage + 1) * pageSize);
 
 
182
  slice.forEach(item => {
183
- const li = document.createElement('li');
184
- const a = document.createElement('a');
185
- a.href = item.url;
186
- a.download = item.name;
187
- a.textContent = item.name;
188
- a.className = 'download-link';
189
- li.appendChild(a);
190
- ul.appendChild(li);
 
 
 
 
 
191
  });
 
192
  document.getElementById('pageInfo').innerText = `第${currentPage+1}/${totalPages}页`;
193
  document.getElementById('prevPage').disabled = currentPage === 0;
194
  document.getElementById('nextPage').disabled = currentPage + 1 >= totalPages;
195
  }
196
 
 
197
  document.getElementById('searchBtn').addEventListener('click', () => {
198
  currentPage = 0;
199
- const name = document.getElementById('searchInput').value;
200
  fetchCompleted(name);
201
  });
 
 
 
 
 
 
 
 
 
202
  document.getElementById('prevPage').addEventListener('click', () => {
203
  if (currentPage > 0) {
204
  currentPage--;
205
- const name = document.getElementById('searchInput').value;
206
  fetchCompleted(name);
207
  }
208
  });
 
209
  document.getElementById('nextPage').addEventListener('click', () => {
 
210
  currentPage++;
211
- const name = document.getElementById('searchInput').value;
212
  fetchCompleted(name);
213
  });
214
 
 
 
 
 
 
 
 
 
 
215
 
216
- // 初始化
217
- fetchTasks();
218
- fetchCompleted();
219
  document.getElementById('refreshCompleted').addEventListener('click', () => {
220
- const name = document.getElementById('searchInput').value;
 
 
 
221
  fetchCompleted(name);
 
 
 
222
  });
223
- // 自动刷新已下载(当无搜索时)
224
- setInterval(() => {
225
- if (!document.getElementById('searchInput').value) fetchCompleted();
226
- }, 5000);
227
 
228
- // 自动刷新队列
229
- setInterval(fetchTasks, 2000);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
230
  </script>
231
- </div>
232
  </body>
233
  </html>
234
  """
 
60
  <head>
61
  <meta charset="UTF-8">
62
  <meta name="viewport" content="width=device-width, initial-scale=1">
63
+ <title>🍅 番茄小说下载器</title>
64
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
65
  <style>
66
+ * {
67
+ margin: 0;
68
+ padding: 0;
69
+ box-sizing: border-box;
70
+ }
71
+
72
+ body {
73
+ font-family: 'SF Pro Display', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
74
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
75
+ min-height: 100vh;
76
+ color: #333;
77
+ line-height: 1.6;
78
+ }
79
+
80
+ .header {
81
+ text-align: center;
82
+ padding: 2rem 0;
83
+ background: rgba(255, 255, 255, 0.1);
84
+ backdrop-filter: blur(10px);
85
+ border-bottom: 1px solid rgba(255, 255, 255, 0.2);
86
+ margin-bottom: 2rem;
87
+ }
88
+
89
+ .header h1 {
90
+ font-size: 2.5rem;
91
+ font-weight: 700;
92
+ color: white;
93
+ text-shadow: 0 2px 4px rgba(0,0,0,0.3);
94
+ margin-bottom: 0.5rem;
95
+ }
96
+
97
+ .header p {
98
+ color: rgba(255, 255, 255, 0.9);
99
+ font-size: 1.1rem;
100
+ }
101
+
102
+ .container {
103
+ max-width: 1200px;
104
+ margin: 0 auto;
105
+ padding: 0 1rem;
106
+ }
107
+
108
+ .grid {
109
+ display: grid;
110
+ grid-template-columns: 1fr 1fr;
111
+ gap: 2rem;
112
+ margin-bottom: 2rem;
113
+ }
114
+
115
+ .card {
116
+ background: rgba(255, 255, 255, 0.95);
117
+ backdrop-filter: blur(10px);
118
+ border-radius: 20px;
119
+ padding: 2rem;
120
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
121
+ border: 1px solid rgba(255, 255, 255, 0.2);
122
+ transition: all 0.3s ease;
123
+ }
124
+
125
+ .card:hover {
126
+ transform: translateY(-5px);
127
+ box-shadow: 0 12px 40px rgba(0, 0, 0, 0.15);
128
+ }
129
+
130
+ .card-title {
131
+ font-size: 1.5rem;
132
+ font-weight: 600;
133
+ color: #2d3748;
134
+ margin-bottom: 1rem;
135
+ display: flex;
136
+ align-items: center;
137
+ gap: 0.5rem;
138
+ }
139
+
140
+ .card-title i {
141
+ color: #667eea;
142
+ }
143
+
144
+ .form-group {
145
+ margin-bottom: 1.5rem;
146
+ }
147
+
148
+ .form-label {
149
+ display: block;
150
+ font-weight: 500;
151
+ color: #4a5568;
152
+ margin-bottom: 0.5rem;
153
+ }
154
+
155
+ .form-input {
156
+ width: 100%;
157
+ padding: 0.75rem 1rem;
158
+ border: 2px solid #e2e8f0;
159
+ border-radius: 12px;
160
+ font-size: 1rem;
161
+ transition: all 0.3s ease;
162
+ background: white;
163
+ }
164
+
165
+ .form-input:focus {
166
+ outline: none;
167
+ border-color: #667eea;
168
+ box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
169
+ }
170
+
171
+ .form-textarea {
172
+ resize: vertical;
173
+ min-height: 120px;
174
+ }
175
+
176
+ .btn {
177
+ display: inline-flex;
178
+ align-items: center;
179
+ justify-content: center;
180
+ gap: 0.5rem;
181
+ padding: 0.75rem 1.5rem;
182
+ border: none;
183
+ border-radius: 12px;
184
+ font-size: 1rem;
185
+ font-weight: 500;
186
+ cursor: pointer;
187
+ transition: all 0.3s ease;
188
+ text-decoration: none;
189
+ }
190
+
191
+ .btn-primary {
192
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
193
+ color: white;
194
+ }
195
+
196
+ .btn-primary:hover {
197
+ transform: translateY(-2px);
198
+ box-shadow: 0 8px 25px rgba(102, 126, 234, 0.3);
199
+ }
200
+
201
+ .btn-secondary {
202
+ background: #f7fafc;
203
+ color: #4a5568;
204
+ border: 2px solid #e2e8f0;
205
+ }
206
+
207
+ .btn-secondary:hover {
208
+ background: #edf2f7;
209
+ border-color: #cbd5e0;
210
+ }
211
+
212
+ .btn-small {
213
+ padding: 0.5rem 1rem;
214
+ font-size: 0.9rem;
215
+ }
216
+
217
+ .progress-container {
218
+ margin-top: 1rem;
219
+ display: none;
220
+ }
221
+
222
+ .progress-bar {
223
+ width: 100%;
224
+ height: 8px;
225
+ background: #e2e8f0;
226
+ border-radius: 4px;
227
+ overflow: hidden;
228
+ margin-bottom: 0.5rem;
229
+ }
230
+
231
+ .progress-fill {
232
+ height: 100%;
233
+ background: linear-gradient(90deg, #667eea, #764ba2);
234
+ border-radius: 4px;
235
+ transition: width 0.3s ease;
236
+ width: 0%;
237
+ }
238
+
239
+ .progress-text {
240
+ text-align: center;
241
+ font-size: 0.9rem;
242
+ color: #4a5568;
243
+ }
244
+
245
+ .download-result {
246
+ margin-top: 1rem;
247
+ }
248
+
249
+ .download-link {
250
+ display: inline-flex;
251
+ align-items: center;
252
+ gap: 0.5rem;
253
+ padding: 0.75rem 1rem;
254
+ background: #f0fff4;
255
+ color: #22543d;
256
+ text-decoration: none;
257
+ border-radius: 8px;
258
+ border: 1px solid #9ae6b4;
259
+ margin-top: 0.5rem;
260
+ transition: all 0.3s ease;
261
+ }
262
+
263
+ .download-link:hover {
264
+ background: #c6f6d5;
265
+ transform: translateX(5px);
266
+ }
267
+
268
+ .list-container {
269
+ background: white;
270
+ border-radius: 12px;
271
+ overflow: hidden;
272
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
273
+ }
274
+
275
+ .list-item {
276
+ padding: 1rem;
277
+ border-bottom: 1px solid #e2e8f0;
278
+ display: flex;
279
+ align-items: center;
280
+ justify-content: space-between;
281
+ transition: background 0.2s ease;
282
+ }
283
+
284
+ .list-item:hover {
285
+ background: #f7fafc;
286
+ }
287
+
288
+ .list-item:last-child {
289
+ border-bottom: none;
290
+ }
291
+
292
+ .status-badge {
293
+ padding: 0.25rem 0.75rem;
294
+ border-radius: 20px;
295
+ font-size: 0.8rem;
296
+ font-weight: 500;
297
+ }
298
+
299
+ .status-queued {
300
+ background: #fef5e7;
301
+ color: #744210;
302
+ }
303
+
304
+ .status-progress {
305
+ background: #e6fffa;
306
+ color: #234e52;
307
+ }
308
+
309
+ .status-done {
310
+ background: #f0fff4;
311
+ color: #22543d;
312
+ }
313
+
314
+ .status-error {
315
+ background: #fed7d7;
316
+ color: #742a2a;
317
+ }
318
+
319
+ .search-container {
320
+ display: flex;
321
+ gap: 0.5rem;
322
+ margin-bottom: 1rem;
323
+ }
324
+
325
+ .pagination {
326
+ display: flex;
327
+ align-items: center;
328
+ justify-content: center;
329
+ gap: 1rem;
330
+ padding: 1rem;
331
+ background: #f7fafc;
332
+ }
333
+
334
+ .full-width {
335
+ grid-column: 1 / -1;
336
+ }
337
+
338
+ @media (max-width: 768px) {
339
+ .grid {
340
+ grid-template-columns: 1fr;
341
+ gap: 1rem;
342
+ }
343
+
344
+ .header h1 {
345
+ font-size: 2rem;
346
+ }
347
+
348
+ .card {
349
+ padding: 1.5rem;
350
+ }
351
+
352
+ .search-container {
353
+ flex-direction: column;
354
+ }
355
  }
 
356
 
357
+ .loading {
358
+ display: inline-block;
359
+ width: 20px;
360
+ height: 20px;
361
+ border: 3px solid #f3f3f3;
362
+ border-top: 3px solid #667eea;
363
+ border-radius: 50%;
364
+ animation: spin 1s linear infinite;
365
+ }
366
+
367
+ @keyframes spin {
368
+ 0% { transform: rotate(0deg); }
369
+ 100% { transform: rotate(360deg); }
370
+ }
371
+
372
+ .fade-in {
373
+ animation: fadeIn 0.5s ease-in;
374
+ }
375
+
376
+ @keyframes fadeIn {
377
+ from { opacity: 0; transform: translateY(20px); }
378
+ to { opacity: 1; transform: translateY(0); }
379
+ }
380
+ </style>
381
  </head>
382
  <body>
383
+ <div class="header">
384
+ <h1><i class="fas fa-book"></i> 番茄小说下载器</h1>
385
+ <p>高效、便捷的小说下载工具</p>
 
 
 
 
 
 
 
386
  </div>
387
+
388
+ <div class="container">
389
+ <div class="grid">
390
+ <!-- 下载区域 -->
391
+ <div class="card">
392
+ <div class="card-title">
393
+ <i class="fas fa-download"></i>
394
+ 开始下载
395
+ </div>
396
+
397
+ <div class="form-group">
398
+ <label class="form-label" for="book_ids">
399
+ <i class="fas fa-list"></i> 小说ID列表
400
+ </label>
401
+ <textarea
402
+ id="book_ids"
403
+ class="form-input form-textarea"
404
+ placeholder="请输入小说ID,支持多个ID用逗号、分号或空格分隔&#10;例如:12345, 67890"
405
+ ></textarea>
406
+ </div>
407
+
408
+ <button id="downloadBtn" class="btn btn-primary" style="width: 100%;">
409
+ <i class="fas fa-rocket"></i>
410
+ 开始下载
411
+ </button>
412
+
413
+ <div id="progress" class="progress-container">
414
+ <div class="progress-bar">
415
+ <div id="progBar" class="progress-fill"></div>
416
+ </div>
417
+ <div id="progText" class="progress-text">0%</div>
418
+ </div>
419
+
420
+ <div id="downloadResult" class="download-result"></div>
421
+ </div>
422
+
423
+ <!-- 下载队列 -->
424
+ <div class="card">
425
+ <div class="card-title">
426
+ <i class="fas fa-clock"></i>
427
+ 下载队列
428
+ <button id="refreshTasks" class="btn btn-secondary btn-small" style="margin-left: auto;">
429
+ <i class="fas fa-sync-alt"></i>
430
+ </button>
431
+ </div>
432
+
433
+ <div id="taskList" class="list-container">
434
+ <div class="list-item">
435
+ <span style="color: #a0aec0;">暂无下载任务</span>
436
+ </div>
437
+ </div>
438
+ </div>
439
+ </div>
440
+
441
+ <!-- 已下载小说 -->
442
+ <div class="card full-width">
443
+ <div class="card-title">
444
+ <i class="fas fa-check-circle"></i>
445
+ 已下载小说
446
+ <button id="refreshCompleted" class="btn btn-secondary btn-small" style="margin-left: auto;">
447
+ <i class="fas fa-sync-alt"></i>
448
+ </button>
449
+ </div>
450
+
451
+ <div class="search-container">
452
+ <input
453
+ type="text"
454
+ id="searchInput"
455
+ class="form-input"
456
+ placeholder="🔍 搜索已下载的小说..."
457
+ style="flex: 1;"
458
+ />
459
+ <button id="searchBtn" class="btn btn-secondary">
460
+ <i class="fas fa-search"></i>
461
+ 搜索
462
+ </button>
463
+ </div>
464
+
465
+ <div id="completedList" class="list-container">
466
+ <div class="list-item">
467
+ <span style="color: #a0aec0;">暂无已下载小说</span>
468
+ </div>
469
+ </div>
470
+
471
+ <div class="pagination">
472
+ <button id="prevPage" class="btn btn-secondary btn-small">
473
+ <i class="fas fa-chevron-left"></i>
474
+ 上一页
475
+ </button>
476
+ <span id="pageInfo">第1/1页</span>
477
+ <button id="nextPage" class="btn btn-secondary btn-small">
478
+ 下一页
479
+ <i class="fas fa-chevron-right"></i>
480
+ </button>
481
+ </div>
482
  </div>
 
483
  </div>
484
  <script>
485
+ // 工具函数
486
+ function showNotification(message, type = 'info') {
487
+ // 简单的通知显示,可以后续扩展
488
+ console.log(`${type.toUpperCase()}: ${message}`);
489
+ }
490
+
491
+ function setButtonLoading(button, loading = true) {
492
+ if (loading) {
493
+ button.disabled = true;
494
+ const icon = button.querySelector('i');
495
+ if (icon) {
496
+ icon.className = 'fas fa-spinner fa-spin';
497
+ }
498
+ } else {
499
+ button.disabled = false;
500
+ const icon = button.querySelector('i');
501
+ if (icon && icon.classList.contains('fa-spinner')) {
502
+ icon.className = 'fas fa-rocket';
503
+ }
504
+ }
505
+ }
506
+
507
+ document.getElementById('downloadBtn').addEventListener('click', async function(){
508
+ const book_ids = document.getElementById('book_ids').value.trim();
509
+ if (!book_ids) {
510
+ showNotification('请输入小说ID', 'warning');
511
  return;
512
  }
513
+
514
+ const btn = this;
515
+ setButtonLoading(btn, true);
516
+
517
+ try {
518
+ // 根据当前队列决定是否排队
519
+ const tasks = await fetch('/tasks').then(res => res.json());
520
+ if (tasks.filter(t => t.status !== 'done').length > 0) {
521
+ const ids = book_ids.split(/[\s,;]+/).filter(id => id);
522
+ for (const bid of ids) {
523
+ await fetch(`/enqueue?book_id=${encodeURIComponent(bid)}`);
524
+ }
525
+ fetchTasks();
526
+ showNotification(`已添加 ${ids.length} 个任务到队列`, 'success');
527
+ setButtonLoading(btn, false);
528
+ return;
529
+ }
530
+
531
+ // 显示进度条
532
+ const progressContainer = document.getElementById('progress');
533
+ progressContainer.style.display = 'block';
534
+ progressContainer.classList.add('fade-in');
535
+
536
+ const response = await fetch(`/download?book_ids=${encodeURIComponent(book_ids)}`);
537
  const total = response.headers.get('Content-Length');
538
  const reader = response.body.getReader();
539
  let received = 0;
540
  const chunks = [];
541
+
542
  function read() {
543
  return reader.read().then(({done, value})=>{
544
  if (done) {
 
550
  const match = cd.match(/filename="?(.+)"?/);
551
  if (match) filename = match[1];
552
  }
553
+
554
+ // 生成下载链接
555
  const link = document.createElement('a');
556
  link.href = url;
557
  link.download = filename;
558
+ link.className = 'download-link fade-in';
559
+ link.innerHTML = `<i class="fas fa-download"></i> ${filename}`;
560
+
561
+ const resultDiv = document.getElementById('downloadResult');
562
+ resultDiv.innerHTML = '';
563
+ resultDiv.appendChild(link);
564
+
565
+ setButtonLoading(btn, false);
566
+ progressContainer.style.display = 'none';
567
+ showNotification('下载完成!', 'success');
568
  return;
569
  }
570
+
571
  chunks.push(value);
572
  received += value.length;
573
  if (total) {
574
  const percent = Math.round(received / total * 100);
575
+ document.getElementById('progBar').style.width = percent + '%';
576
  document.getElementById('progText').innerText = percent + '%';
577
  }
578
  return read();
579
  });
580
  }
581
  read();
582
+ } catch (error) {
583
+ setButtonLoading(btn, false);
584
+ document.getElementById('progress').style.display = 'none';
585
+ showNotification('下载失败:' + error.message, 'error');
586
+ }
587
  });
588
  // 刷新队列和已下载列表
589
  function fetchTasks() {
590
  fetch('/tasks').then(res => res.json()).then(data => {
591
+ const container = document.getElementById('taskList');
592
+ const activeTasks = data.filter(task => task.status !== 'done');
593
+
594
+ if (activeTasks.length === 0) {
595
+ container.innerHTML = '<div class="list-item"><span style="color: #a0aec0;"><i class="fas fa-check"></i> 暂无下载任务</span></div>';
596
+ return;
597
+ }
598
+
599
+ container.innerHTML = '';
600
+ activeTasks.forEach(task => {
601
+ const div = document.createElement('div');
602
+ div.className = 'list-item fade-in';
603
+
604
+ const statusClass = {
605
+ 'queued': 'status-queued',
606
+ 'in-progress': 'status-progress',
607
+ 'error': 'status-error'
608
+ }[task.status] || 'status-queued';
609
+
610
+ const statusIcon = {
611
+ 'queued': 'fas fa-clock',
612
+ 'in-progress': 'fas fa-spinner fa-spin',
613
+ 'error': 'fas fa-exclamation-triangle'
614
+ }[task.status] || 'fas fa-clock';
615
+
616
+ const statusText = {
617
+ 'queued': '排队中',
618
+ 'in-progress': '下载中',
619
+ 'error': '失败'
620
+ }[task.status] || '未知';
621
+
622
+ div.innerHTML = `
623
+ <span><i class="fas fa-book"></i> ${task.name || task.book_id}</span>
624
+ <span class="status-badge ${statusClass}">
625
+ <i class="${statusIcon}"></i> ${statusText}
626
+ </span>
627
+ `;
628
+ container.appendChild(div);
629
+ });
630
+ }).catch(error => {
631
+ console.error('获取任务列表失败:', error);
632
  });
633
  }
634
+
635
  // 分页和搜索设置
636
  let currentPage = 0;
637
  const pageSize = 10;
 
640
  const url = name ? `/search?name=${encodeURIComponent(name)}` : '/search';
641
  fetch(url).then(res => res.json()).then(data => {
642
  renderCompleted(data);
643
+ }).catch(error => {
644
+ console.error('获取已下载列表失败:', error);
645
  });
646
  }
647
 
648
  function renderCompleted(data) {
649
+ const container = document.getElementById('completedList');
650
+
651
+ if (data.length === 0) {
652
+ container.innerHTML = '<div class="list-item"><span style="color: #a0aec0;"><i class="fas fa-inbox"></i> 暂无已下载小说</span></div>';
653
+ document.getElementById('pageInfo').innerText = '第1/1页';
654
+ document.getElementById('prevPage').disabled = true;
655
+ document.getElementById('nextPage').disabled = true;
656
+ return;
657
+ }
658
+
659
  const total = data.length;
660
  const totalPages = Math.ceil(total / pageSize) || 1;
661
  if (currentPage >= totalPages) currentPage = totalPages - 1;
662
+ if (currentPage < 0) currentPage = 0;
663
+
664
  const slice = data.slice(currentPage * pageSize, (currentPage + 1) * pageSize);
665
+
666
+ container.innerHTML = '';
667
  slice.forEach(item => {
668
+ const div = document.createElement('div');
669
+ div.className = 'list-item fade-in';
670
+
671
+ const link = document.createElement('a');
672
+ link.href = item.url;
673
+ link.download = item.name;
674
+ link.className = 'download-link';
675
+ link.innerHTML = `<i class="fas fa-download"></i> ${item.name}`;
676
+ link.style.textDecoration = 'none';
677
+ link.style.color = 'inherit';
678
+
679
+ div.appendChild(link);
680
+ container.appendChild(div);
681
  });
682
+
683
  document.getElementById('pageInfo').innerText = `第${currentPage+1}/${totalPages}页`;
684
  document.getElementById('prevPage').disabled = currentPage === 0;
685
  document.getElementById('nextPage').disabled = currentPage + 1 >= totalPages;
686
  }
687
 
688
+ // 事件监听器
689
  document.getElementById('searchBtn').addEventListener('click', () => {
690
  currentPage = 0;
691
+ const name = document.getElementById('searchInput').value.trim();
692
  fetchCompleted(name);
693
  });
694
+
695
+ document.getElementById('searchInput').addEventListener('keypress', (e) => {
696
+ if (e.key === 'Enter') {
697
+ currentPage = 0;
698
+ const name = e.target.value.trim();
699
+ fetchCompleted(name);
700
+ }
701
+ });
702
+
703
  document.getElementById('prevPage').addEventListener('click', () => {
704
  if (currentPage > 0) {
705
  currentPage--;
706
+ const name = document.getElementById('searchInput').value.trim();
707
  fetchCompleted(name);
708
  }
709
  });
710
+
711
  document.getElementById('nextPage').addEventListener('click', () => {
712
+ if (document.getElementById('nextPage').disabled) return;
713
  currentPage++;
714
+ const name = document.getElementById('searchInput').value.trim();
715
  fetchCompleted(name);
716
  });
717
 
718
+ document.getElementById('refreshTasks').addEventListener('click', () => {
719
+ const btn = document.getElementById('refreshTasks');
720
+ const icon = btn.querySelector('i');
721
+ icon.classList.add('fa-spin');
722
+ fetchTasks();
723
+ setTimeout(() => {
724
+ icon.classList.remove('fa-spin');
725
+ }, 1000);
726
+ });
727
 
 
 
 
728
  document.getElementById('refreshCompleted').addEventListener('click', () => {
729
+ const btn = document.getElementById('refreshCompleted');
730
+ const icon = btn.querySelector('i');
731
+ icon.classList.add('fa-spin');
732
+ const name = document.getElementById('searchInput').value.trim();
733
  fetchCompleted(name);
734
+ setTimeout(() => {
735
+ icon.classList.remove('fa-spin');
736
+ }, 1000);
737
  });
 
 
 
 
738
 
739
+ // 初始化
740
+ document.addEventListener('DOMContentLoaded', () => {
741
+ fetchTasks();
742
+ fetchCompleted();
743
+
744
+ // 自动刷新已下载(当无搜索时)
745
+ setInterval(() => {
746
+ if (!document.getElementById('searchInput').value.trim()) {
747
+ fetchCompleted();
748
+ }
749
+ }, 10000);
750
+
751
+ // 自动刷新队列
752
+ setInterval(fetchTasks, 3000);
753
+
754
+ // 添加一些交互效果
755
+ document.querySelectorAll('.card').forEach(card => {
756
+ card.addEventListener('mouseenter', () => {
757
+ card.style.transform = 'translateY(-2px)';
758
+ });
759
+ card.addEventListener('mouseleave', () => {
760
+ card.style.transform = 'translateY(0)';
761
+ });
762
+ });
763
+ });
764
  </script>
 
765
  </body>
766
  </html>
767
  """