Nanny7 commited on
Commit
2505849
·
1 Parent(s): 3fb05c7

重构下载队列显示逻辑和控制机制

Browse files

新功能:
- 队列显示所有任务状态(排队中、下载中、已完成、失败)
- 详细任务信息显示:小说名称 - ID - 添加时间 - 状态
- 完整的任务生命周期管理和时间记录
- 任务管理中心替代简单下载队列

控制机制改进:
- 移除前端队列判断逻辑,完全由后台队列系统管理
- 自动任务调度和优先级控制
- 智能队列处理和状态同步

状态管理优化:
- 实时状态更新(排队中/下载中/已完成/失败)
- 不同状态的视觉标识(颜色、图标、动画)
- 当前下载任务高亮显示

时间记录功能:
- 记录任务添加时间、开始时间、完成时间
- 本地时间格式显示
- 完整的任务历史追踪

用户体验提升:
- 任务管理中心成为核心功能
- 清晰的任务生命周期可视化
- 更好的错误处理和重试机制
- 已完成任务直接下载链接

Files changed (2) hide show
  1. __pycache__/server.cpython-313.pyc +0 -0
  2. server.py +97 -193
__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
@@ -13,15 +13,23 @@ TASK_QUEUE = queue.Queue()
13
  STATUS = {}
14
  FILE_PATHS = {}
15
  NAMES = {}
 
16
 
17
  # 后台下载任务处理线程
18
  def process_queue():
 
 
19
  while True:
20
  try:
21
  book_id = TASK_QUEUE.get(timeout=1) # 添加超时避免无限等待
22
  if book_id in STATUS and STATUS[book_id] != "queued":
23
  continue # 跳过已经处理过的任务
24
 
 
 
 
 
 
25
  STATUS[book_id] = "in-progress"
26
  save_path = os.path.join(DOWNLOAD_ROOT, book_id)
27
  os.makedirs(save_path, exist_ok=True)
@@ -36,17 +44,28 @@ def process_queue():
36
  filename = f"{name}.txt"
37
  path = os.path.join(save_path, filename)
38
 
 
 
 
39
  if os.path.exists(path):
40
  FILE_PATHS[book_id] = f"/files/{book_id}/{filename}"
41
  NAMES[book_id] = name
42
  STATUS[book_id] = "done"
 
 
 
43
  print(f"下载完成: {name}")
44
  else:
45
  STATUS[book_id] = "error"
 
 
46
  print(f"下载失败: 文件不存在 - {book_id}")
47
 
48
  except Exception as e:
49
  STATUS[book_id] = "error"
 
 
 
50
  print(f"下载异常: {book_id} - {str(e)}")
51
 
52
  except:
@@ -293,9 +312,10 @@ def root():
293
  padding: 1rem;
294
  border-bottom: 1px solid #e2e8f0;
295
  display: flex;
296
- align-items: center;
297
  justify-content: space-between;
298
  transition: background 0.2s ease;
 
299
  }
300
 
301
  .list-item:hover {
@@ -453,11 +473,11 @@ def root():
453
  <div id="downloadResult" class="download-result"></div>
454
  </div>
455
 
456
- <!-- 下载队列 -->
457
  <div class="card">
458
  <div class="card-title">
459
- <i class="fas fa-clock"></i>
460
- 下载队列
461
  <button id="refreshTasks" class="btn btn-secondary btn-small" style="margin-left: auto;">
462
  <i class="fas fa-sync-alt"></i>
463
  </button>
@@ -612,7 +632,7 @@ def root():
612
  // 解析输入的ID
613
  const ids = book_ids.split(/[\s,;]+/).filter(id => id);
614
 
615
- // 将所有ID加入队列
616
  for (const bid of ids) {
617
  await fetch(`/enqueue?book_id=${encodeURIComponent(bid)}`);
618
  }
@@ -622,21 +642,7 @@ def root():
622
 
623
  // 立即刷新队列显示
624
  fetchTasks();
625
- showNotification(`已添加 ${ids.length} 个任务到队列`, 'success');
626
-
627
- // 检查是否有正在进行的任务
628
- const tasks = await fetch('/tasks').then(res => res.json());
629
- const activeTasks = tasks.filter(t => t.status === 'in-progress');
630
-
631
- // 如果没有正在进行的任务,开始处理第一个排队的任务
632
- if (activeTasks.length === 0) {
633
- const queuedTasks = tasks.filter(t => t.status === 'queued');
634
- if (queuedTasks.length > 0) {
635
- // 开始下载第一个排队的任务
636
- const firstTask = queuedTasks[0];
637
- startDirectDownload(firstTask.book_id);
638
- }
639
- }
640
 
641
  setButtonLoading(btn, false);
642
  } catch (error) {
@@ -645,154 +651,8 @@ def root():
645
  }
646
  });
647
 
648
- // 全局变量跟踪下载状态
649
- let isDownloading = false;
650
  let currentDownloadId = null;
651
-
652
- // 直接下载函数
653
- async function startDirectDownload(bookId) {
654
- if (isDownloading) {
655
- console.log('已有下载任务在进行中,跳过');
656
- return;
657
- }
658
-
659
- isDownloading = true;
660
- currentDownloadId = bookId;
661
-
662
- try {
663
- // 重置并显示进度条
664
- const progressContainer = document.getElementById('progress');
665
- const progBar = document.getElementById('progBar');
666
- const progText = document.getElementById('progText');
667
-
668
- progBar.style.width = '0%';
669
- progText.innerText = '0%';
670
- progressContainer.style.display = 'block';
671
- progressContainer.classList.add('fade-in');
672
-
673
- // 清空之前的下载结果
674
- const resultDiv = document.getElementById('downloadResult');
675
- resultDiv.innerHTML = '';
676
-
677
- showNotification(`开始下载小说 ${NAMES[bookId] || bookId}...`, 'info');
678
-
679
- const response = await fetch(`/download?book_ids=${encodeURIComponent(bookId)}`);
680
-
681
- if (!response.ok) {
682
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
683
- }
684
-
685
- const total = response.headers.get('Content-Length');
686
- const reader = response.body.getReader();
687
- let received = 0;
688
- const chunks = [];
689
-
690
- function read() {
691
- return reader.read().then(({done, value})=>{
692
- if (done) {
693
- try {
694
- const blob = new Blob(chunks, {type: response.headers.get('Content-Type')});
695
- const url = URL.createObjectURL(blob);
696
- const cd = response.headers.get('Content-Disposition');
697
- let filename = 'download.txt';
698
- if (cd) {
699
- const match = cd.match(/filename[*]?=['"]?([^'";\r\n]+)['"]?/);
700
- if (match) filename = decodeURIComponent(match[1]);
701
- }
702
-
703
- // 生成下载链接
704
- const link = document.createElement('a');
705
- link.href = url;
706
- link.download = filename;
707
- link.className = 'download-link fade-in';
708
- link.innerHTML = `<i class="fas fa-download"></i> ${filename}`;
709
-
710
- resultDiv.innerHTML = '';
711
- resultDiv.appendChild(link);
712
-
713
- // 隐藏进度条
714
- progressContainer.style.display = 'none';
715
-
716
- showNotification(`下载完成:${filename}`, 'success');
717
-
718
- // 标记下载完成
719
- isDownloading = false;
720
- currentDownloadId = null;
721
-
722
- // 刷新任务列表和已下载列表
723
- fetchTasks();
724
- fetchCompleted();
725
-
726
- // 检查是否还有排队的任务
727
- setTimeout(async () => {
728
- try {
729
- const tasks = await fetch('/tasks').then(res => res.json());
730
- const queuedTasks = tasks.filter(t => t.status === 'queued');
731
- if (queuedTasks.length > 0 && !isDownloading) {
732
- startDirectDownload(queuedTasks[0].book_id);
733
- }
734
- } catch (error) {
735
- console.error('检查队列任务失败:', error);
736
- }
737
- }, 1000);
738
-
739
- } catch (error) {
740
- console.error('处理下载结果失败:', error);
741
- showNotification('处理下载结果失败', 'error');
742
- }
743
- return;
744
- }
745
-
746
- chunks.push(value);
747
- received += value.length;
748
-
749
- // 更新进度条
750
- if (total && parseInt(total) > 0) {
751
- const percent = Math.min(100, Math.round((received / parseInt(total)) * 100));
752
- progBar.style.width = percent + '%';
753
- progText.innerText = percent + '%';
754
- } else {
755
- // 如果没有总长度信息,显示已接收的数据量
756
- const mb = (received / (1024 * 1024)).toFixed(1);
757
- progText.innerText = `已下载 ${mb} MB`;
758
- // 使用动画效果显示进度
759
- const animatedPercent = Math.min(90, (received / 1000000) * 10); // 假设进度
760
- progBar.style.width = animatedPercent + '%';
761
- }
762
-
763
- return read();
764
- });
765
- }
766
-
767
- await read();
768
-
769
- } catch (error) {
770
- console.error('下载失败:', error);
771
-
772
- // 隐藏进度条
773
- const progressContainer = document.getElementById('progress');
774
- progressContainer.style.display = 'none';
775
-
776
- showNotification(`下载失败:${error.message}`, 'error');
777
-
778
- // 标记下载完成(即使失败)
779
- isDownloading = false;
780
- currentDownloadId = null;
781
-
782
- // 即使失败也要继续处理下一个任务
783
- setTimeout(async () => {
784
- try {
785
- const tasks = await fetch('/tasks').then(res => res.json());
786
- const queuedTasks = tasks.filter(t => t.status === 'queued');
787
- if (queuedTasks.length > 0 && !isDownloading) {
788
- startDirectDownload(queuedTasks[0].book_id);
789
- }
790
- } catch (error) {
791
- console.error('检查队列任务失败:', error);
792
- }
793
- }, 2000);
794
- }
795
- }
796
  // 全局变量存储任务名称
797
  let NAMES = {};
798
 
@@ -800,7 +660,6 @@ def root():
800
  function fetchTasks() {
801
  fetch('/tasks').then(res => res.json()).then(data => {
802
  const container = document.getElementById('taskList');
803
- const activeTasks = data.filter(task => task.status !== 'done');
804
 
805
  // 更新全局名称缓存
806
  data.forEach(task => {
@@ -809,13 +668,13 @@ def root():
809
  }
810
  });
811
 
812
- if (activeTasks.length === 0) {
813
- container.innerHTML = '<div class="list-item"><span style="color: #a0aec0;"><i class="fas fa-check"></i> 暂无下载任务</span></div>';
814
  return;
815
  }
816
 
817
  container.innerHTML = '';
818
- activeTasks.forEach((task, index) => {
819
  const div = document.createElement('div');
820
  div.className = 'list-item fade-in';
821
  div.setAttribute('data-book-id', task.book_id);
@@ -823,18 +682,21 @@ def root():
823
  const statusClass = {
824
  'queued': 'status-queued',
825
  'in-progress': 'status-progress',
 
826
  'error': 'status-error'
827
  }[task.status] || 'status-queued';
828
 
829
  const statusIcon = {
830
  'queued': 'fas fa-clock',
831
  'in-progress': 'fas fa-spinner fa-spin',
 
832
  'error': 'fas fa-exclamation-triangle'
833
  }[task.status] || 'fas fa-clock';
834
 
835
  const statusText = {
836
- 'queued': index === 0 ? '即将开始' : '排队中',
837
  'in-progress': '下载中',
 
838
  'error': '失败'
839
  }[task.status] || '未知';
840
 
@@ -842,12 +704,41 @@ def root():
842
  const isCurrentDownload = task.book_id === currentDownloadId;
843
  const extraClass = isCurrentDownload ? ' current-download' : '';
844
 
 
 
 
 
 
845
  div.innerHTML = `
846
- <span><i class="fas fa-book"></i> ${task.name || task.book_id}</span>
 
 
 
 
 
 
 
 
 
 
 
847
  <span class="status-badge ${statusClass}${extraClass}">
848
- <i class="${statusIcon}"></i> ${statusText}
849
  </span>
850
  `;
 
 
 
 
 
 
 
 
 
 
 
 
 
851
  container.appendChild(div);
852
  });
853
  }).catch(error => {
@@ -976,18 +867,11 @@ def root():
976
  // 自动刷新队列 - 更频繁的刷新以保持同步
977
  setInterval(() => {
978
  fetchTasks();
979
- // 如果有下载任务在进行,检查是否需要启动下一个
980
- if (!isDownloading) {
981
- fetch('/tasks').then(res => res.json()).then(tasks => {
982
- const queuedTasks = tasks.filter(t => t.status === 'queued');
983
- const inProgressTasks = tasks.filter(t => t.status === 'in-progress');
984
-
985
- // 如果没有进行中的任务但有排队的任务,启动下载
986
- if (inProgressTasks.length === 0 && queuedTasks.length > 0) {
987
- startDirectDownload(queuedTasks[0].book_id);
988
- }
989
- }).catch(console.error);
990
- }
991
  }, 2000);
992
 
993
  // 添加一些交互效果
@@ -1016,16 +900,12 @@ def root():
1016
  @app.get("/enqueue")
1017
  def enqueue(book_id: str):
1018
  """添加到下载队列"""
 
 
1019
  # 如果已有任务在队列或处理中
1020
  if STATUS.get(book_id) in ("queued", "in-progress"):
1021
  return {"message": "已在队列", "status": STATUS[book_id]}
1022
 
1023
- # 如果任务已完成,重新设置为排队状态
1024
- if STATUS.get(book_id) == "done":
1025
- STATUS[book_id] = "queued"
1026
- TASK_QUEUE.put(book_id)
1027
- return {"message": "重新添加到下载队列", "status": "queued"}
1028
-
1029
  # 获取书籍信息并设置名称
1030
  try:
1031
  name, _, _ = get_book_info(book_id, get_headers())
@@ -1034,6 +914,15 @@ def enqueue(book_id: str):
1034
  except:
1035
  NAMES[book_id] = f"小说_{book_id}"
1036
 
 
 
 
 
 
 
 
 
 
1037
  TASK_QUEUE.put(book_id)
1038
  STATUS[book_id] = "queued"
1039
  return {"message": "已添加到下载队列", "status": "queued"}
@@ -1053,7 +942,22 @@ def task_status(book_id: str):
1053
  @app.get("/tasks")
1054
  def tasks():
1055
  """获取所有任务"""
1056
- return [{"book_id": bid, "status": status, "name": NAMES.get(bid, "")} for bid, status in STATUS.items()]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1057
 
1058
  @app.get("/search")
1059
  def search(name: str = ""):
 
13
  STATUS = {}
14
  FILE_PATHS = {}
15
  NAMES = {}
16
+ TASK_DETAILS = {} # 存储任务详细信息,包括时间戳等
17
 
18
  # 后台下载任务处理线程
19
  def process_queue():
20
+ from datetime import datetime
21
+
22
  while True:
23
  try:
24
  book_id = TASK_QUEUE.get(timeout=1) # 添加超时避免无限等待
25
  if book_id in STATUS and STATUS[book_id] != "queued":
26
  continue # 跳过已经处理过的任务
27
 
28
+ # 记录开始时间
29
+ start_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
30
+ if book_id in TASK_DETAILS:
31
+ TASK_DETAILS[book_id]["start_time"] = start_time
32
+
33
  STATUS[book_id] = "in-progress"
34
  save_path = os.path.join(DOWNLOAD_ROOT, book_id)
35
  os.makedirs(save_path, exist_ok=True)
 
44
  filename = f"{name}.txt"
45
  path = os.path.join(save_path, filename)
46
 
47
+ # 记录完成时间
48
+ complete_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
49
+
50
  if os.path.exists(path):
51
  FILE_PATHS[book_id] = f"/files/{book_id}/{filename}"
52
  NAMES[book_id] = name
53
  STATUS[book_id] = "done"
54
+ if book_id in TASK_DETAILS:
55
+ TASK_DETAILS[book_id]["complete_time"] = complete_time
56
+ TASK_DETAILS[book_id]["name"] = name
57
  print(f"下载完成: {name}")
58
  else:
59
  STATUS[book_id] = "error"
60
+ if book_id in TASK_DETAILS:
61
+ TASK_DETAILS[book_id]["complete_time"] = complete_time
62
  print(f"下载失败: 文件不存在 - {book_id}")
63
 
64
  except Exception as e:
65
  STATUS[book_id] = "error"
66
+ complete_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
67
+ if book_id in TASK_DETAILS:
68
+ TASK_DETAILS[book_id]["complete_time"] = complete_time
69
  print(f"下载异常: {book_id} - {str(e)}")
70
 
71
  except:
 
312
  padding: 1rem;
313
  border-bottom: 1px solid #e2e8f0;
314
  display: flex;
315
+ align-items: flex-start;
316
  justify-content: space-between;
317
  transition: background 0.2s ease;
318
+ min-height: 80px;
319
  }
320
 
321
  .list-item:hover {
 
473
  <div id="downloadResult" class="download-result"></div>
474
  </div>
475
 
476
+ <!-- 任务管理中心 -->
477
  <div class="card">
478
  <div class="card-title">
479
+ <i class="fas fa-tasks"></i>
480
+ 任务管理中心
481
  <button id="refreshTasks" class="btn btn-secondary btn-small" style="margin-left: auto;">
482
  <i class="fas fa-sync-alt"></i>
483
  </button>
 
632
  // 解析输入的ID
633
  const ids = book_ids.split(/[\s,;]+/).filter(id => id);
634
 
635
+ // 将所有ID加入队列,队列系统会自动管理执行
636
  for (const bid of ids) {
637
  await fetch(`/enqueue?book_id=${encodeURIComponent(bid)}`);
638
  }
 
642
 
643
  // 立即刷新队列显示
644
  fetchTasks();
645
+ showNotification(`已添加 ${ids.length} 个任务到队列,队列系统将自动处理`, 'success');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
646
 
647
  setButtonLoading(btn, false);
648
  } catch (error) {
 
651
  }
652
  });
653
 
654
+ // 全局变量跟踪当前下载状态(仅用于UI显示)
 
655
  let currentDownloadId = null;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
656
  // 全局变量存储任务名称
657
  let NAMES = {};
658
 
 
660
  function fetchTasks() {
661
  fetch('/tasks').then(res => res.json()).then(data => {
662
  const container = document.getElementById('taskList');
 
663
 
664
  // 更新全局名称缓存
665
  data.forEach(task => {
 
668
  }
669
  });
670
 
671
+ if (data.length === 0) {
672
+ container.innerHTML = '<div class="list-item"><span style="color: #a0aec0;"><i class="fas fa-inbox"></i> 暂无任务</span></div>';
673
  return;
674
  }
675
 
676
  container.innerHTML = '';
677
+ data.forEach((task, index) => {
678
  const div = document.createElement('div');
679
  div.className = 'list-item fade-in';
680
  div.setAttribute('data-book-id', task.book_id);
 
682
  const statusClass = {
683
  'queued': 'status-queued',
684
  'in-progress': 'status-progress',
685
+ 'done': 'status-done',
686
  'error': 'status-error'
687
  }[task.status] || 'status-queued';
688
 
689
  const statusIcon = {
690
  'queued': 'fas fa-clock',
691
  'in-progress': 'fas fa-spinner fa-spin',
692
+ 'done': 'fas fa-check-circle',
693
  'error': 'fas fa-exclamation-triangle'
694
  }[task.status] || 'fas fa-clock';
695
 
696
  const statusText = {
697
+ 'queued': '排队中',
698
  'in-progress': '下载中',
699
+ 'done': '已完成',
700
  'error': '失败'
701
  }[task.status] || '未知';
702
 
 
704
  const isCurrentDownload = task.book_id === currentDownloadId;
705
  const extraClass = isCurrentDownload ? ' current-download' : '';
706
 
707
+ // 构建任务信息显示
708
+ const taskInfo = `${task.name || task.book_id} - ID: [${task.book_id}]`;
709
+ const timeInfo = `添加时间: ${task.add_time}`;
710
+ const statusInfo = `状态: ${statusText}`;
711
+
712
  div.innerHTML = `
713
+ <div style="flex: 1;">
714
+ <div style="font-weight: 500; margin-bottom: 4px;">
715
+ <i class="fas fa-book"></i> ${taskInfo}
716
+ </div>
717
+ <div style="font-size: 0.8rem; color: #6b7280; margin-bottom: 2px;">
718
+ <i class="fas fa-clock"></i> ${timeInfo}
719
+ </div>
720
+ <div style="font-size: 0.8rem; color: #6b7280;">
721
+ ${task.start_time ? `<i class="fas fa-play"></i> 开始: ${task.start_time}` : ''}
722
+ ${task.complete_time ? `<i class="fas fa-flag-checkered"></i> 完成: ${task.complete_time}` : ''}
723
+ </div>
724
+ </div>
725
  <span class="status-badge ${statusClass}${extraClass}">
726
+ <i class="${statusIcon}"></i> ${statusInfo}
727
  </span>
728
  `;
729
+
730
+ // 如果是已完成的任务,添加下载链接
731
+ if (task.status === 'done') {
732
+ const downloadBtn = document.createElement('a');
733
+ downloadBtn.href = `/files/${task.book_id}/${task.name}.txt`;
734
+ downloadBtn.download = `${task.name}.txt`;
735
+ downloadBtn.className = 'btn btn-secondary btn-small';
736
+ downloadBtn.style.marginLeft = '8px';
737
+ downloadBtn.innerHTML = '<i class="fas fa-download"></i>';
738
+ downloadBtn.title = '下载文件';
739
+ div.querySelector('.status-badge').parentNode.appendChild(downloadBtn);
740
+ }
741
+
742
  container.appendChild(div);
743
  });
744
  }).catch(error => {
 
867
  // 自动刷新队列 - 更频繁的刷新以保持同步
868
  setInterval(() => {
869
  fetchTasks();
870
+ // 更新当前下载任务ID用于UI显示
871
+ fetch('/tasks').then(res => res.json()).then(tasks => {
872
+ const inProgressTasks = tasks.filter(t => t.status === 'in-progress');
873
+ currentDownloadId = inProgressTasks.length > 0 ? inProgressTasks[0].book_id : null;
874
+ }).catch(console.error);
 
 
 
 
 
 
 
875
  }, 2000);
876
 
877
  // 添加一些交互效果
 
900
  @app.get("/enqueue")
901
  def enqueue(book_id: str):
902
  """添加到下载队列"""
903
+ from datetime import datetime
904
+
905
  # 如果已有任务在队列或处理中
906
  if STATUS.get(book_id) in ("queued", "in-progress"):
907
  return {"message": "已在队列", "status": STATUS[book_id]}
908
 
 
 
 
 
 
 
909
  # 获取书籍信息并设置名称
910
  try:
911
  name, _, _ = get_book_info(book_id, get_headers())
 
914
  except:
915
  NAMES[book_id] = f"小说_{book_id}"
916
 
917
+ # 记录任务详细信息
918
+ current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
919
+ TASK_DETAILS[book_id] = {
920
+ "add_time": current_time,
921
+ "start_time": None,
922
+ "complete_time": None,
923
+ "name": NAMES[book_id]
924
+ }
925
+
926
  TASK_QUEUE.put(book_id)
927
  STATUS[book_id] = "queued"
928
  return {"message": "已添加到下载队列", "status": "queued"}
 
942
  @app.get("/tasks")
943
  def tasks():
944
  """获取所有任务"""
945
+ task_list = []
946
+ for book_id, status in STATUS.items():
947
+ task_detail = TASK_DETAILS.get(book_id, {})
948
+ task = {
949
+ "book_id": book_id,
950
+ "name": NAMES.get(book_id, book_id),
951
+ "status": status,
952
+ "add_time": task_detail.get("add_time", "未知"),
953
+ "start_time": task_detail.get("start_time"),
954
+ "complete_time": task_detail.get("complete_time")
955
+ }
956
+ task_list.append(task)
957
+
958
+ # 按添加时间排序,最新的在前面
959
+ task_list.sort(key=lambda x: x["add_time"], reverse=True)
960
+ return task_list
961
 
962
  @app.get("/search")
963
  def search(name: str = ""):