feat: CellData 容量处理集成改进与前端处理进度优化
This commit is contained in:
@@ -151,7 +151,7 @@ def _run_remote_processing(
|
||||
|
||||
state.history_manager.update(task_id, file_count=download_result.file_count)
|
||||
|
||||
refresh_cell_data(app_config, work_dir, logger)
|
||||
_try_refresh_cell_data(app_config, work_dir, logger)
|
||||
|
||||
if app_config.warehouse_type == "metrix":
|
||||
started = time.time()
|
||||
@@ -210,6 +210,21 @@ def _run_remote_processing(
|
||||
on_finish(task_id, final_status)
|
||||
|
||||
|
||||
def _try_refresh_cell_data(app_config: AppConfig, work_dir: Path, logger: ProcessLogger) -> None:
|
||||
if not app_config.cell_data.remote_data.enabled:
|
||||
return
|
||||
logger.set_stage("cell_data")
|
||||
logger.info("── CellData 更新 ──")
|
||||
try:
|
||||
result = refresh_cell_data(app_config, work_dir, logger)
|
||||
logger.success(
|
||||
f"CellData 更新完成:{result.imported_rows} 行"
|
||||
f"(解析 {result.parsed_rows},跳过 {result.skipped_rows})"
|
||||
)
|
||||
except Exception as exc:
|
||||
logger.warning(f"CellData 更新失败,继续容量处理: {exc}")
|
||||
|
||||
|
||||
def _format_bytes(size: int) -> str:
|
||||
value = float(size)
|
||||
for unit in ("B", "KB", "MB", "GB"):
|
||||
|
||||
@@ -169,7 +169,7 @@ def _task_finished(task_id: str) -> bool:
|
||||
|
||||
def _run_processing(task_id: str, work_dir: Path, logger: ProcessLogger, app_config: AppConfig) -> None:
|
||||
try:
|
||||
refresh_cell_data(app_config, work_dir, logger)
|
||||
_try_refresh_cell_data(app_config, work_dir, logger)
|
||||
if app_config.warehouse_type == "metrix":
|
||||
import time
|
||||
|
||||
@@ -214,3 +214,18 @@ def _run_processing(task_id: str, work_dir: Path, logger: ProcessLogger, app_con
|
||||
finally:
|
||||
apply_history_retention_safely()
|
||||
state.reset_task_lock()
|
||||
|
||||
|
||||
def _try_refresh_cell_data(app_config: AppConfig, work_dir: Path, logger: ProcessLogger) -> None:
|
||||
if not app_config.cell_data.remote_data.enabled:
|
||||
return
|
||||
logger.set_stage("cell_data")
|
||||
logger.info("── CellData 更新 ──")
|
||||
try:
|
||||
result = refresh_cell_data(app_config, work_dir, logger)
|
||||
logger.success(
|
||||
f"CellData 更新完成:{result.imported_rows} 行"
|
||||
f"(解析 {result.parsed_rows},跳过 {result.skipped_rows})"
|
||||
)
|
||||
except Exception as exc:
|
||||
logger.warning(f"CellData 更新失败,继续容量处理: {exc}")
|
||||
|
||||
@@ -846,3 +846,11 @@
|
||||
|
||||
- `/api/remote/test`、`/api/config/cell-data/remote/test`、`/api/config/cell-data/mysql/test` 改为同步路由函数,保持响应结构不变,但让 FastAPI 在线程池中执行 FTP/SFTP、Metrix 平台 HTTP 与 MySQL 连接测试,避免慢连接或超时阻塞事件循环。
|
||||
- 验证:`.venv\Scripts\python.exe -m compileall -q app` 通过;`frontend` `npm run build` 通过;构建产物已清理。
|
||||
|
||||
## 2026-06-25:CellData 容量处理集成改进与前端处理进度优化
|
||||
|
||||
- 容量处理集成:`tasks.py` 和 `remote.py` 中的 `refresh_cell_data()` 调用改为 `_try_refresh_cell_data()` 包装函数,CellData 数据源未启用时直接跳过,CellData 处理失败时记录警告但继续执行容量处理,不再因 CellData SFTP 连接失败或无数据等原因导致整个容量处理任务失败。
|
||||
- CellData 阶段标识:容量处理流程中 CellData 更新阶段会设置独立的 `cell_data` stage,前端可显示"更新 CellData..."状态文案;CellData 完成后日志输出导入行数、解析行数和跳过行数摘要。
|
||||
- 前端阶段标签:`stageLabels` 新增 `cell_data: '更新 CellData...'`;`importing` 标签从"上传数据中..."改为"导入数据中..."以避免与文件上传混淆。
|
||||
- 前端结果摘要:CellData 独立处理或容量处理完成后,处理进度区域显示成功摘要(文件数、导入行数、跳过行数、耗时);`TaskStatus` 类型新增 `result?: CellDataResult` 字段。
|
||||
- 验证:`python -m compileall -q app` 通过;`frontend` `npm run build`(vue-tsc)通过;构建产物已清理。
|
||||
|
||||
@@ -213,6 +213,9 @@
|
||||
<n-alert v-if="activeTask?.has_active" type="info" :bordered="false" class="task-alert">
|
||||
当前任务:{{ activeTask.task_id }} / {{ currentStageText }}
|
||||
</n-alert>
|
||||
<n-alert v-if="taskStatus?.status === 'completed' && taskResultSummary" type="success" :bordered="false" class="task-alert">
|
||||
{{ taskResultSummary }}
|
||||
</n-alert>
|
||||
<n-alert v-if="taskStatus?.error" type="error" :bordered="false" class="task-alert">
|
||||
{{ taskStatus.error }}
|
||||
</n-alert>
|
||||
@@ -326,7 +329,7 @@ import {
|
||||
} from '@vicons/ionicons5';
|
||||
|
||||
import { ApiRequestError, apiGet, apiPost, upload } from '../api/client';
|
||||
import type { ActiveTask, ApiErrorDetail, LicenseStatus, TaskStatus } from '../types';
|
||||
import type { ActiveTask, ApiErrorDetail, CellDataResult, LicenseStatus, TaskStatus } from '../types';
|
||||
import { toColoredLogLines } from '../composables/logLines';
|
||||
|
||||
type FileStatus = 'pending' | 'uploading' | 'uploaded' | 'error';
|
||||
@@ -380,6 +383,7 @@ let timer: number | undefined;
|
||||
|
||||
const stageLabels: Record<string, string> = {
|
||||
license: '授权校验中...',
|
||||
cell_data: '更新 CellData...',
|
||||
locating: '定位数据中...',
|
||||
downloading: '远程下载中...',
|
||||
uploading: '上传文件中...',
|
||||
@@ -387,7 +391,7 @@ const stageLabels: Record<string, string> = {
|
||||
extracting: '解压数据中...',
|
||||
converting: '转换 Excel 中...',
|
||||
parsing: '解析数据中...',
|
||||
importing: '上传数据中...',
|
||||
importing: '导入数据中...',
|
||||
scripting: '运行脚本中...',
|
||||
completed: '处理完成',
|
||||
failed: '处理失败'
|
||||
@@ -420,6 +424,18 @@ const processStatusText = computed(() => {
|
||||
if (taskStatus.value?.status === 'failed') return '处理失败';
|
||||
return currentStageText.value;
|
||||
});
|
||||
const taskResultSummary = computed(() => {
|
||||
const status = taskStatus.value;
|
||||
if (!status || status.status !== 'completed') return '';
|
||||
const parts: string[] = [];
|
||||
if (status.result) {
|
||||
parts.push(formatCellDataResult(status.result));
|
||||
}
|
||||
if (status.elapsed_time != null) {
|
||||
parts.push(`耗时 ${status.elapsed_time.toFixed(1)}s`);
|
||||
}
|
||||
return parts.join(',');
|
||||
});
|
||||
|
||||
watch(
|
||||
() => taskStatus.value?.logs?.length ?? 0,
|
||||
@@ -969,6 +985,14 @@ function fileStatusText(item: PickedFile): string {
|
||||
return '等待上传';
|
||||
}
|
||||
|
||||
function formatCellDataResult(result: CellDataResult): string {
|
||||
const parts: string[] = [];
|
||||
if (result.selected_files > 0) parts.push(`${result.selected_files} 个文件`);
|
||||
parts.push(`导入 ${result.imported_rows} 行`);
|
||||
if (result.skipped_rows > 0) parts.push(`跳过 ${result.skipped_rows} 行`);
|
||||
return parts.join(',');
|
||||
}
|
||||
|
||||
function formatBytes(size: number): string {
|
||||
if (size === 0) return '0 B';
|
||||
const units = ['B', 'KB', 'MB', 'GB'];
|
||||
|
||||
@@ -27,6 +27,13 @@ export interface LicenseStatus {
|
||||
zip_count?: number;
|
||||
}
|
||||
|
||||
export interface CellDataResult {
|
||||
selected_files: number;
|
||||
parsed_rows: number;
|
||||
imported_rows: number;
|
||||
skipped_rows: number;
|
||||
}
|
||||
|
||||
export interface TaskStatus {
|
||||
task_id: string;
|
||||
status: 'pending' | 'processing' | 'completed' | 'failed' | string;
|
||||
@@ -35,6 +42,7 @@ export interface TaskStatus {
|
||||
elapsed_time?: number;
|
||||
error?: string;
|
||||
error_detail?: ApiErrorDetail;
|
||||
result?: CellDataResult;
|
||||
}
|
||||
|
||||
export interface ActiveTask {
|
||||
|
||||
Reference in New Issue
Block a user