feat: 优化处理进度和日志跟随
This commit is contained in:
@@ -38,15 +38,25 @@ async def start_remote_processing():
|
|||||||
work_dir.mkdir(parents=True, exist_ok=True)
|
work_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
logs: list[str] = []
|
logs: list[str] = []
|
||||||
|
current_stage = "downloading"
|
||||||
|
|
||||||
def log_callback(message: str) -> None:
|
def log_callback(message: str) -> None:
|
||||||
logs.append(message)
|
logs.append(message)
|
||||||
state.processing_tasks[task_id] = {"logs": logs.copy(), "status": "processing"}
|
_set_task_stage(task_id, current_stage, logs)
|
||||||
|
|
||||||
logger = ProcessLogger(log_file=work_dir / "log.txt", callback=log_callback)
|
def stage_callback(stage: str) -> None:
|
||||||
|
nonlocal current_stage
|
||||||
|
current_stage = stage
|
||||||
|
_set_task_stage(task_id, current_stage, logs)
|
||||||
|
|
||||||
|
logger = ProcessLogger(
|
||||||
|
log_file=work_dir / "log.txt",
|
||||||
|
callback=log_callback,
|
||||||
|
stage_callback=stage_callback,
|
||||||
|
)
|
||||||
state.history_manager.create(work_dir, 0, record_id=task_id)
|
state.history_manager.create(work_dir, 0, record_id=task_id)
|
||||||
state.history_manager.update(task_id, status="processing")
|
state.history_manager.update(task_id, status="processing")
|
||||||
state.processing_tasks[task_id] = {"logs": [], "status": "processing"}
|
state.processing_tasks[task_id] = {"logs": [], "status": "processing", "stage": current_stage}
|
||||||
state.global_task_lock.update(
|
state.global_task_lock.update(
|
||||||
{
|
{
|
||||||
"locked": True,
|
"locked": True,
|
||||||
@@ -71,6 +81,16 @@ async def start_remote_processing():
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _set_task_stage(task_id: str, stage: str, logs: list[str], status: str = "processing") -> None:
|
||||||
|
state.processing_tasks[task_id] = {
|
||||||
|
"logs": logs.copy(),
|
||||||
|
"status": status,
|
||||||
|
"stage": stage,
|
||||||
|
}
|
||||||
|
if state.global_task_lock["task_id"] == task_id:
|
||||||
|
state.global_task_lock["stage"] = stage
|
||||||
|
|
||||||
|
|
||||||
def _run_remote_processing(
|
def _run_remote_processing(
|
||||||
task_id: str,
|
task_id: str,
|
||||||
work_dir: Path,
|
work_dir: Path,
|
||||||
@@ -93,7 +113,7 @@ def _run_remote_processing(
|
|||||||
raise RuntimeError("远程目录中未下载到任何文件")
|
raise RuntimeError("远程目录中未下载到任何文件")
|
||||||
|
|
||||||
state.history_manager.update(task_id, file_count=download_result.file_count)
|
state.history_manager.update(task_id, file_count=download_result.file_count)
|
||||||
state.global_task_lock["stage"] = "processing"
|
logger.set_stage("extracting")
|
||||||
|
|
||||||
processor = DataProcessor(state.config, work_dir, logger)
|
processor = DataProcessor(state.config, work_dir, logger)
|
||||||
result = processor.process()
|
result = processor.process()
|
||||||
@@ -115,6 +135,7 @@ def _run_remote_processing(
|
|||||||
state.processing_tasks[task_id] = {
|
state.processing_tasks[task_id] = {
|
||||||
"logs": state.history_manager.get_logs(task_id),
|
"logs": state.history_manager.get_logs(task_id),
|
||||||
"status": status,
|
"status": status,
|
||||||
|
"stage": status,
|
||||||
}
|
}
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.error(f"远程自动化任务失败: {exc}")
|
logger.error(f"远程自动化任务失败: {exc}")
|
||||||
@@ -122,6 +143,7 @@ def _run_remote_processing(
|
|||||||
state.processing_tasks[task_id] = {
|
state.processing_tasks[task_id] = {
|
||||||
"logs": state.history_manager.get_logs(task_id),
|
"logs": state.history_manager.get_logs(task_id),
|
||||||
"status": "failed",
|
"status": "failed",
|
||||||
|
"stage": "failed",
|
||||||
}
|
}
|
||||||
finally:
|
finally:
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ async def get_global_task_status():
|
|||||||
return {
|
return {
|
||||||
"has_active": True,
|
"has_active": True,
|
||||||
"task_id": task_id,
|
"task_id": task_id,
|
||||||
"stage": "processing",
|
"stage": active_tasks[task_id].get("stage", "processing"),
|
||||||
"logs": active_tasks[task_id].get("logs", []),
|
"logs": active_tasks[task_id].get("logs", []),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,14 +88,24 @@ async def start_processing(task_id: str = Body(..., embed=True)):
|
|||||||
raise HTTPException(status_code=400, detail="工作目录不存在")
|
raise HTTPException(status_code=400, detail="工作目录不存在")
|
||||||
|
|
||||||
logs: list[str] = []
|
logs: list[str] = []
|
||||||
|
current_stage = "processing"
|
||||||
|
|
||||||
def log_callback(message: str) -> None:
|
def log_callback(message: str) -> None:
|
||||||
logs.append(message)
|
logs.append(message)
|
||||||
state.processing_tasks[task_id] = {"logs": logs.copy(), "status": "processing"}
|
_set_task_stage(task_id, current_stage, logs)
|
||||||
|
|
||||||
logger = ProcessLogger(log_file=work_dir / "log.txt", callback=log_callback)
|
def stage_callback(stage: str) -> None:
|
||||||
|
nonlocal current_stage
|
||||||
|
current_stage = stage
|
||||||
|
_set_task_stage(task_id, current_stage, logs)
|
||||||
|
|
||||||
|
logger = ProcessLogger(
|
||||||
|
log_file=work_dir / "log.txt",
|
||||||
|
callback=log_callback,
|
||||||
|
stage_callback=stage_callback,
|
||||||
|
)
|
||||||
state.history_manager.update(task_id, status="processing")
|
state.history_manager.update(task_id, status="processing")
|
||||||
state.processing_tasks[task_id] = {"logs": [], "status": "processing"}
|
state.processing_tasks[task_id] = {"logs": [], "status": "processing", "stage": current_stage}
|
||||||
state.global_task_lock.update(
|
state.global_task_lock.update(
|
||||||
{
|
{
|
||||||
"locked": True,
|
"locked": True,
|
||||||
@@ -116,7 +126,12 @@ async def get_processing_status(task_id: str = Body(..., embed=True)):
|
|||||||
if task_id in state.processing_tasks:
|
if task_id in state.processing_tasks:
|
||||||
task_info = state.processing_tasks[task_id]
|
task_info = state.processing_tasks[task_id]
|
||||||
logs = task_info.get("logs") or state.history_manager.get_logs(task_id)
|
logs = task_info.get("logs") or state.history_manager.get_logs(task_id)
|
||||||
return {"task_id": task_id, "status": task_info["status"], "logs": logs}
|
return {
|
||||||
|
"task_id": task_id,
|
||||||
|
"status": task_info["status"],
|
||||||
|
"stage": task_info.get("stage"),
|
||||||
|
"logs": logs,
|
||||||
|
}
|
||||||
|
|
||||||
record = state.history_manager.get(task_id)
|
record = state.history_manager.get(task_id)
|
||||||
if not record:
|
if not record:
|
||||||
@@ -125,6 +140,7 @@ async def get_processing_status(task_id: str = Body(..., embed=True)):
|
|||||||
return {
|
return {
|
||||||
"task_id": task_id,
|
"task_id": task_id,
|
||||||
"status": record.status,
|
"status": record.status,
|
||||||
|
"stage": record.status,
|
||||||
"logs": state.history_manager.get_logs(task_id),
|
"logs": state.history_manager.get_logs(task_id),
|
||||||
"elapsed_time": record.elapsed_time,
|
"elapsed_time": record.elapsed_time,
|
||||||
"error": record.error,
|
"error": record.error,
|
||||||
@@ -140,6 +156,16 @@ def _task_finished(task_id: str) -> bool:
|
|||||||
return bool(task_info and task_info.get("status") in {"completed", "failed"})
|
return bool(task_info and task_info.get("status") in {"completed", "failed"})
|
||||||
|
|
||||||
|
|
||||||
|
def _set_task_stage(task_id: str, stage: str, logs: list[str], status: str = "processing") -> None:
|
||||||
|
state.processing_tasks[task_id] = {
|
||||||
|
"logs": logs.copy(),
|
||||||
|
"status": status,
|
||||||
|
"stage": stage,
|
||||||
|
}
|
||||||
|
if state.global_task_lock["task_id"] == task_id:
|
||||||
|
state.global_task_lock["stage"] = stage
|
||||||
|
|
||||||
|
|
||||||
def _run_processing(task_id: str, work_dir: Path, logger: ProcessLogger) -> None:
|
def _run_processing(task_id: str, work_dir: Path, logger: ProcessLogger) -> None:
|
||||||
try:
|
try:
|
||||||
processor = DataProcessor(state.config, work_dir, logger)
|
processor = DataProcessor(state.config, work_dir, logger)
|
||||||
@@ -155,12 +181,14 @@ def _run_processing(task_id: str, work_dir: Path, logger: ProcessLogger) -> None
|
|||||||
state.processing_tasks[task_id] = {
|
state.processing_tasks[task_id] = {
|
||||||
"logs": state.history_manager.get_logs(task_id),
|
"logs": state.history_manager.get_logs(task_id),
|
||||||
"status": status,
|
"status": status,
|
||||||
|
"stage": status,
|
||||||
}
|
}
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
state.history_manager.update(task_id, status="failed", error=str(exc))
|
state.history_manager.update(task_id, status="failed", error=str(exc))
|
||||||
state.processing_tasks[task_id] = {
|
state.processing_tasks[task_id] = {
|
||||||
"logs": state.history_manager.get_logs(task_id),
|
"logs": state.history_manager.get_logs(task_id),
|
||||||
"status": "failed",
|
"status": "failed",
|
||||||
|
"stage": "failed",
|
||||||
}
|
}
|
||||||
finally:
|
finally:
|
||||||
try:
|
try:
|
||||||
|
|||||||
+17
-1
@@ -22,10 +22,16 @@ from app.database import DatabaseManager
|
|||||||
class ProcessLogger:
|
class ProcessLogger:
|
||||||
"""处理日志记录器"""
|
"""处理日志记录器"""
|
||||||
|
|
||||||
def __init__(self, log_file: Optional[Path] = None, callback: Optional[Callable[[str], None]] = None):
|
def __init__(
|
||||||
|
self,
|
||||||
|
log_file: Optional[Path] = None,
|
||||||
|
callback: Optional[Callable[[str], None]] = None,
|
||||||
|
stage_callback: Optional[Callable[[str], None]] = None,
|
||||||
|
):
|
||||||
self.logs: List[str] = []
|
self.logs: List[str] = []
|
||||||
self.log_file = log_file
|
self.log_file = log_file
|
||||||
self.callback = callback
|
self.callback = callback
|
||||||
|
self.stage_callback = stage_callback
|
||||||
# 如果指定了日志文件,确保目录存在
|
# 如果指定了日志文件,确保目录存在
|
||||||
if self.log_file:
|
if self.log_file:
|
||||||
self.log_file.parent.mkdir(parents=True, exist_ok=True)
|
self.log_file.parent.mkdir(parents=True, exist_ok=True)
|
||||||
@@ -62,6 +68,10 @@ class ProcessLogger:
|
|||||||
def success(self, message: str):
|
def success(self, message: str):
|
||||||
self.log(message, "SUCCESS")
|
self.log(message, "SUCCESS")
|
||||||
|
|
||||||
|
def set_stage(self, stage: str):
|
||||||
|
if self.stage_callback:
|
||||||
|
self.stage_callback(stage)
|
||||||
|
|
||||||
def get_logs(self) -> List[str]:
|
def get_logs(self) -> List[str]:
|
||||||
return self.logs.copy()
|
return self.logs.copy()
|
||||||
|
|
||||||
@@ -195,24 +205,30 @@ class DataProcessor:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
# 1. 解压 ZIP 文件
|
# 1. 解压 ZIP 文件
|
||||||
|
self.logger.set_stage("extracting")
|
||||||
self._unzip_files()
|
self._unzip_files()
|
||||||
|
|
||||||
# 2. 处理 Excel 文件(并行)
|
# 2. 处理 Excel 文件(并行)
|
||||||
|
self.logger.set_stage("converting")
|
||||||
self._process_excel_files_parallel()
|
self._process_excel_files_parallel()
|
||||||
|
|
||||||
# 3. 处理 CSV 文件并上传到数据库(高性能批量插入)
|
# 3. 处理 CSV 文件并上传到数据库(高性能批量插入)
|
||||||
|
self.logger.set_stage("importing")
|
||||||
self._process_csv_files()
|
self._process_csv_files()
|
||||||
|
|
||||||
# 4. 执行 SQL 脚本
|
# 4. 执行 SQL 脚本
|
||||||
|
self.logger.set_stage("scripting")
|
||||||
self._execute_sql_script()
|
self._execute_sql_script()
|
||||||
|
|
||||||
elapsed = round(time.time() - start_time, 2)
|
elapsed = round(time.time() - start_time, 2)
|
||||||
|
self.logger.set_stage("completed")
|
||||||
self.logger.success(f"处理完成!总耗时: {elapsed} 秒")
|
self.logger.success(f"处理完成!总耗时: {elapsed} 秒")
|
||||||
|
|
||||||
self.results["success"] = True
|
self.results["success"] = True
|
||||||
self.results["elapsed_time"] = elapsed
|
self.results["elapsed_time"] = elapsed
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
self.logger.set_stage("failed")
|
||||||
self.logger.error(f"处理失败: {str(e)}")
|
self.logger.error(f"处理失败: {str(e)}")
|
||||||
self.results["success"] = False
|
self.results["success"] = False
|
||||||
self.results["error"] = str(e)
|
self.results["error"] = str(e)
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
# 项目上下文记录
|
# 项目上下文记录
|
||||||
|
|
||||||
|
## 2026-05-19:优化处理进度阶段显示和日志跟随
|
||||||
|
|
||||||
|
- `ProcessLogger` 新增轻量阶段回调,`DataProcessor.process()` 会在远程下载后依次上报 `extracting`、`converting`、`importing`、`scripting`、`completed/failed` 阶段。
|
||||||
|
- `/api/process/status` 和 `/api/task/status` 返回当前 `stage`,本地上传处理和远程下载处理都通过 `state.processing_tasks` 与全局任务锁同步阶段,前端轮询即可实时显示“远程下载中 / 解压数据中 / 上传数据中 / 运行脚本中”等状态。
|
||||||
|
- `frontend/src/components/FileWorkflow.vue` 的处理进度卡片新增“保持最新 Log”勾选框,勾选后新日志到达会自动滚动到日志底部;当前任务提示不再显示原始阶段码,改为中文阶段文本。
|
||||||
|
- 已执行 `.venv\Scripts\python.exe -m compileall app`、`uvx --offline ruff check .` 和 `npm run build`,均通过。
|
||||||
|
|
||||||
## 2026-05-19:清理后端冗余代码和未用依赖
|
## 2026-05-19:清理后端冗余代码和未用依赖
|
||||||
|
|
||||||
- `app/database.py` 移除未使用的 SQLAlchemy 连接池、`engine` 属性、`dispose()` 空释放路径和未引用的 `delete_rows()`;数据库访问统一保留现有 PyMySQL 上下文连接。
|
- `app/database.py` 移除未使用的 SQLAlchemy 连接池、`engine` 属性、`dispose()` 空释放路径和未引用的 `delete_rows()`;数据库访问统一保留现有 PyMySQL 上下文连接。
|
||||||
|
|||||||
@@ -126,6 +126,7 @@
|
|||||||
<span class="process-status" :class="statusClass">{{ processStatusText }}</span>
|
<span class="process-status" :class="statusClass">{{ processStatusText }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-header-right">
|
<div class="card-header-right">
|
||||||
|
<n-checkbox v-model:checked="keepLatestLog" size="small">保持最新 Log</n-checkbox>
|
||||||
<n-button size="small" secondary @click="checkActiveTask">
|
<n-button size="small" secondary @click="checkActiveTask">
|
||||||
<template #icon><n-icon><RefreshOutline /></n-icon></template>
|
<template #icon><n-icon><RefreshOutline /></n-icon></template>
|
||||||
刷新
|
刷新
|
||||||
@@ -134,12 +135,12 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<n-alert v-if="activeTask?.has_active" type="info" :bordered="false" class="task-alert">
|
<n-alert v-if="activeTask?.has_active" type="info" :bordered="false" class="task-alert">
|
||||||
当前任务:{{ activeTask.task_id }} / {{ activeTask.stage || 'processing' }}
|
当前任务:{{ activeTask.task_id }} / {{ currentStageText }}
|
||||||
</n-alert>
|
</n-alert>
|
||||||
<n-alert v-if="taskStatus?.error" type="error" :bordered="false" class="task-alert">
|
<n-alert v-if="taskStatus?.error" type="error" :bordered="false" class="task-alert">
|
||||||
{{ taskStatus.error }}
|
{{ taskStatus.error }}
|
||||||
</n-alert>
|
</n-alert>
|
||||||
<div class="log-container">
|
<div ref="logContainer" class="log-container">
|
||||||
<pre class="log-content">{{ logText }}</pre>
|
<pre class="log-content">{{ logText }}</pre>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -149,7 +150,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onBeforeUnmount, onMounted, ref } from 'vue';
|
import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue';
|
||||||
import { useMessage } from 'naive-ui';
|
import { useMessage } from 'naive-ui';
|
||||||
import {
|
import {
|
||||||
CloudDownloadOutline,
|
CloudDownloadOutline,
|
||||||
@@ -196,8 +197,22 @@ const remoteStarting = ref(false);
|
|||||||
const isDragging = ref(false);
|
const isDragging = ref(false);
|
||||||
const taskStatus = ref<TaskStatus | null>(null);
|
const taskStatus = ref<TaskStatus | null>(null);
|
||||||
const activeTask = ref<ActiveTask | null>(null);
|
const activeTask = ref<ActiveTask | null>(null);
|
||||||
|
const keepLatestLog = ref(false);
|
||||||
|
const logContainer = ref<HTMLElement | null>(null);
|
||||||
let timer: number | undefined;
|
let timer: number | undefined;
|
||||||
|
|
||||||
|
const stageLabels: Record<string, string> = {
|
||||||
|
downloading: '远程下载中...',
|
||||||
|
uploading: '上传文件中...',
|
||||||
|
processing: '处理中...',
|
||||||
|
extracting: '解压数据中...',
|
||||||
|
converting: '转换 Excel 中...',
|
||||||
|
importing: '上传数据中...',
|
||||||
|
scripting: '运行脚本中...',
|
||||||
|
completed: '处理完成',
|
||||||
|
failed: '处理失败'
|
||||||
|
};
|
||||||
|
|
||||||
const logText = computed(() => taskStatus.value?.logs?.join('\n') || '等待任务开始');
|
const logText = computed(() => taskStatus.value?.logs?.join('\n') || '等待任务开始');
|
||||||
const processVisible = computed(() => Boolean(taskStatus.value || activeTask.value?.has_active));
|
const processVisible = computed(() => Boolean(taskStatus.value || activeTask.value?.has_active));
|
||||||
const taskInProgress = computed(() => {
|
const taskInProgress = computed(() => {
|
||||||
@@ -214,12 +229,27 @@ const statusClass = computed(() => {
|
|||||||
if (taskStatus.value?.status === 'failed') return 'failed';
|
if (taskStatus.value?.status === 'failed') return 'failed';
|
||||||
return 'processing';
|
return 'processing';
|
||||||
});
|
});
|
||||||
|
const currentStage = computed(() => taskStatus.value?.stage || activeTask.value?.stage || 'processing');
|
||||||
|
const currentStageText = computed(() => stageText(currentStage.value));
|
||||||
const processStatusText = computed(() => {
|
const processStatusText = computed(() => {
|
||||||
if (taskStatus.value?.status === 'completed') return '处理完成';
|
if (taskStatus.value?.status === 'completed') return '处理完成';
|
||||||
if (taskStatus.value?.status === 'failed') return '处理失败';
|
if (taskStatus.value?.status === 'failed') return '处理失败';
|
||||||
if (activeTask.value?.stage === 'downloading') return '远程下载中...';
|
return currentStageText.value;
|
||||||
if (activeTask.value?.stage === 'uploading') return '上传中...';
|
});
|
||||||
return '处理中...';
|
|
||||||
|
watch(
|
||||||
|
() => taskStatus.value?.logs?.length ?? 0,
|
||||||
|
() => {
|
||||||
|
if (keepLatestLog.value) {
|
||||||
|
void nextTick(scrollLogToBottom);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(keepLatestLog, checked => {
|
||||||
|
if (checked) {
|
||||||
|
void nextTick(scrollLogToBottom);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
@@ -422,7 +452,7 @@ async function uploadAndStart() {
|
|||||||
});
|
});
|
||||||
message.success(`上传完成:${result.file_count ?? files.value.length} 个文件`);
|
message.success(`上传完成:${result.file_count ?? files.value.length} 个文件`);
|
||||||
await apiPost('/api/process/start', { task_id: result.task_id });
|
await apiPost('/api/process/start', { task_id: result.task_id });
|
||||||
taskStatus.value = { task_id: result.task_id, status: 'processing', logs: ['任务已提交,等待处理日志...'] };
|
taskStatus.value = { task_id: result.task_id, status: 'processing', stage: 'processing', logs: ['任务已提交,等待处理日志...'] };
|
||||||
startPolling(result.task_id);
|
startPolling(result.task_id);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
files.value.forEach(item => {
|
files.value.forEach(item => {
|
||||||
@@ -451,7 +481,12 @@ async function startRemoteProcessing() {
|
|||||||
stage: result.stage || 'downloading',
|
stage: result.stage || 'downloading',
|
||||||
started_at: new Date().toISOString()
|
started_at: new Date().toISOString()
|
||||||
};
|
};
|
||||||
taskStatus.value = { task_id: result.task_id, status: 'processing', logs: ['远程下载任务已提交,等待处理日志...'] };
|
taskStatus.value = {
|
||||||
|
task_id: result.task_id,
|
||||||
|
status: 'processing',
|
||||||
|
stage: result.stage || 'downloading',
|
||||||
|
logs: ['远程下载任务已提交,等待处理日志...']
|
||||||
|
};
|
||||||
startPolling(result.task_id);
|
startPolling(result.task_id);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.error(error instanceof Error ? error.message : '启动远程自动化任务失败');
|
message.error(error instanceof Error ? error.message : '启动远程自动化任务失败');
|
||||||
@@ -495,6 +530,9 @@ function stopPolling() {
|
|||||||
async function poll(taskId: string) {
|
async function poll(taskId: string) {
|
||||||
try {
|
try {
|
||||||
taskStatus.value = await apiPost<TaskStatus>('/api/process/status', { task_id: taskId });
|
taskStatus.value = await apiPost<TaskStatus>('/api/process/status', { task_id: taskId });
|
||||||
|
if (activeTask.value?.has_active && taskStatus.value.stage) {
|
||||||
|
activeTask.value = { ...activeTask.value, stage: taskStatus.value.stage };
|
||||||
|
}
|
||||||
if (['completed', 'failed'].includes(taskStatus.value.status)) {
|
if (['completed', 'failed'].includes(taskStatus.value.status)) {
|
||||||
stopPolling();
|
stopPolling();
|
||||||
await checkActiveTask();
|
await checkActiveTask();
|
||||||
@@ -505,6 +543,18 @@ async function poll(taskId: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function stageText(stage?: string | null): string {
|
||||||
|
if (!stage) return '处理中...';
|
||||||
|
return stageLabels[stage] || '处理中...';
|
||||||
|
}
|
||||||
|
|
||||||
|
function scrollLogToBottom() {
|
||||||
|
const container = logContainer.value;
|
||||||
|
if (container) {
|
||||||
|
container.scrollTop = container.scrollHeight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function fileStatusText(item: PickedFile): string {
|
function fileStatusText(item: PickedFile): string {
|
||||||
if (item.status === 'uploading') return `上传中 ${item.progress}%`;
|
if (item.status === 'uploading') return `上传中 ${item.progress}%`;
|
||||||
if (item.status === 'uploaded') return '已完成';
|
if (item.status === 'uploaded') return '已完成';
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ export interface ApiError {
|
|||||||
export interface TaskStatus {
|
export interface TaskStatus {
|
||||||
task_id: string;
|
task_id: string;
|
||||||
status: 'pending' | 'processing' | 'completed' | 'failed' | string;
|
status: 'pending' | 'processing' | 'completed' | 'failed' | string;
|
||||||
|
stage?: string | null;
|
||||||
logs: string[];
|
logs: string[];
|
||||||
elapsed_time?: number;
|
elapsed_time?: number;
|
||||||
error?: string;
|
error?: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user