fix: 优化日志显示和下载完成反馈
This commit is contained in:
@@ -487,14 +487,14 @@ async function downloadSelectedCsv() {
|
||||
const filename = `${selectedCsvTable.value}.csv`;
|
||||
downloadingFormat.value = 'csv';
|
||||
try {
|
||||
const completed = await download(
|
||||
const result = await download(
|
||||
'/api/download',
|
||||
{ table_name: selectedCsvTable.value, format: 'csv' },
|
||||
filename
|
||||
);
|
||||
if (completed) {
|
||||
if (result.saved) {
|
||||
csvDialogVisible.value = false;
|
||||
showDownloadCompleteDialog(dialog, filename);
|
||||
showDownloadCompleteDialog(dialog, filename, result.path);
|
||||
}
|
||||
} catch (error) {
|
||||
message.error(error instanceof Error ? error.message : '导出失败');
|
||||
@@ -510,14 +510,14 @@ async function downloadSelectedXlsx() {
|
||||
downloadingFormat.value = 'xlsx';
|
||||
const filename = selectedXlsxTables.value.length === 1 ? `${selectedXlsxTables.value[0]}.xlsx` : 'tables.xlsx';
|
||||
try {
|
||||
const completed = await download(
|
||||
const result = await download(
|
||||
'/api/download',
|
||||
{ table_names: selectedXlsxTables.value, format: 'xlsx' },
|
||||
filename
|
||||
);
|
||||
if (completed) {
|
||||
if (result.saved) {
|
||||
xlsxDialogVisible.value = false;
|
||||
showDownloadCompleteDialog(dialog, filename);
|
||||
showDownloadCompleteDialog(dialog, filename, result.path);
|
||||
}
|
||||
} catch (error) {
|
||||
message.error(error instanceof Error ? error.message : '导出失败');
|
||||
|
||||
@@ -141,7 +141,12 @@
|
||||
{{ taskStatus.error }}
|
||||
</n-alert>
|
||||
<div ref="logContainer" class="log-container">
|
||||
<pre class="log-content">{{ logText }}</pre>
|
||||
<pre class="colored-log-content"><span
|
||||
v-for="(line, index) in logLines"
|
||||
:key="index"
|
||||
class="colored-log-line"
|
||||
:class="`log-level-${line.level}`"
|
||||
>{{ line.text }}</span></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -189,6 +194,7 @@ import {
|
||||
|
||||
import { ApiRequestError, apiGet, apiPost, upload } from '../api/client';
|
||||
import type { ActiveTask, ApiErrorDetail, LicenseStatus, TaskStatus } from '../types';
|
||||
import { toColoredLogLines } from '../composables/logLines';
|
||||
|
||||
type FileStatus = 'pending' | 'uploading' | 'uploaded' | 'error';
|
||||
|
||||
@@ -248,6 +254,7 @@ const stageLabels: Record<string, string> = {
|
||||
};
|
||||
|
||||
const logText = computed(() => taskStatus.value?.logs?.join('\n') || '等待任务开始');
|
||||
const logLines = computed(() => toColoredLogLines(logText.value, '等待任务开始'));
|
||||
const processVisible = computed(() => Boolean(taskStatus.value || activeTask.value?.has_active));
|
||||
const taskInProgress = computed(() => {
|
||||
if (activeTask.value?.has_active) {
|
||||
|
||||
@@ -101,10 +101,9 @@ import { CopyOutline, DownloadOutline, FileTrayOutline, RefreshOutline, TrashOut
|
||||
import { apiGet, apiPost, download as downloadFile } from '../api/client';
|
||||
import type { CacheSize, HistoryDetail, HistoryRecord } from '../types';
|
||||
import { showDownloadCompleteDialog } from '../composables/downloadFeedback';
|
||||
import { toColoredLogLines } from '../composables/logLines';
|
||||
import { resetPageHeader, setPageHeader } from '../composables/pageHeader';
|
||||
|
||||
type LogLevel = 'default' | 'info' | 'success' | 'warning' | 'error';
|
||||
|
||||
const message = useMessage();
|
||||
const dialog = useDialog();
|
||||
const records = ref<HistoryRecord[]>([]);
|
||||
@@ -120,13 +119,7 @@ let recordSizeToken = 0;
|
||||
const totalSizeText = computed(() => `总占用: ${cacheSize.value?.size_formatted || '计算中...'}`);
|
||||
const hasRecords = computed(() => records.value.length > 0);
|
||||
const detailLogText = computed(() => detail.value?.logs?.join('\n') || '');
|
||||
const detailLogLines = computed(() => {
|
||||
const lines = detailLogText.value ? detailLogText.value.split(/\r?\n/) : ['暂无日志'];
|
||||
return lines.map(line => ({
|
||||
text: line,
|
||||
level: resolveLogLevel(line)
|
||||
}));
|
||||
});
|
||||
const detailLogLines = computed(() => toColoredLogLines(detailLogText.value));
|
||||
|
||||
onMounted(() => {
|
||||
setPageHeader({
|
||||
@@ -186,9 +179,9 @@ async function downloadHistory(record: HistoryRecord) {
|
||||
downloadingRecordId.value = record.id;
|
||||
try {
|
||||
const filename = `${record.id}.zip`;
|
||||
const completed = await downloadFile('/api/history/download', { record_id: record.id }, filename);
|
||||
if (completed) {
|
||||
showDownloadCompleteDialog(dialog, filename);
|
||||
const result = await downloadFile('/api/history/download', { record_id: record.id }, filename);
|
||||
if (result.saved) {
|
||||
showDownloadCompleteDialog(dialog, filename, result.path);
|
||||
}
|
||||
} catch (error) {
|
||||
message.error(error instanceof Error ? error.message : '下载历史数据失败');
|
||||
@@ -256,14 +249,6 @@ async function writeClipboardText(text: string) {
|
||||
}
|
||||
}
|
||||
|
||||
function resolveLogLevel(line: string): LogLevel {
|
||||
if (/\b(ERROR|FAILED|FAILURE)\b|错误|失败/i.test(line)) return 'error';
|
||||
if (/\b(WARN|WARNING)\b|警告/i.test(line)) return 'warning';
|
||||
if (/\b(SUCCESS|SUCCEEDED|COMPLETED)\b|成功|完成/i.test(line)) return 'success';
|
||||
if (/\bINFO\b|信息/i.test(line)) return 'info';
|
||||
return 'default';
|
||||
}
|
||||
|
||||
function confirmDelete(recordId: string) {
|
||||
dialog.warning({
|
||||
title: '删除历史记录',
|
||||
|
||||
@@ -4,14 +4,8 @@
|
||||
<div ref="editorHost" class="script-editor" />
|
||||
</div>
|
||||
|
||||
<div class="script-status-bar">
|
||||
<span>{{ cursorText }}</span>
|
||||
<span>{{ editorStatus }}</span>
|
||||
<span>{{ lineCount }} 行</span>
|
||||
</div>
|
||||
|
||||
<div v-if="taskStatus" class="process-section script-process-section">
|
||||
<div class="upload-process-card">
|
||||
<div v-if="taskStatus || taskId" class="script-process-section">
|
||||
<div class="upload-process-card script-process-card">
|
||||
<div class="card-header">
|
||||
<div class="card-header-left">
|
||||
<span class="card-title">脚本执行进度</span>
|
||||
@@ -22,10 +16,23 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<n-log :log="logText" language="text" trim class="script-log" />
|
||||
<div class="colored-log-panel script-log" role="log" aria-label="脚本执行日志">
|
||||
<pre class="colored-log-content"><span
|
||||
v-for="(line, index) in logLines"
|
||||
:key="index"
|
||||
class="colored-log-line"
|
||||
:class="`log-level-${line.level}`"
|
||||
>{{ line.text }}</span></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="script-status-bar">
|
||||
<span>{{ cursorText }}</span>
|
||||
<span>{{ editorStatus }}</span>
|
||||
<span>{{ lineCount }} 行</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -39,12 +46,17 @@ import 'monaco-editor/esm/vs/basic-languages/sql/sql.contribution';
|
||||
|
||||
import { apiGet, apiPost } from '../api/client';
|
||||
import type { ApiMessage, ScriptContent, TaskStatus } from '../types';
|
||||
import { toColoredLogLines } from '../composables/logLines';
|
||||
import { resetPageHeader, setPageHeader } from '../composables/pageHeader';
|
||||
|
||||
type MonacoGlobal = typeof globalThis & {
|
||||
MonacoEnvironment?: {
|
||||
getWorker: (_moduleId: string, _label: string) => Worker;
|
||||
};
|
||||
__capacityReportScriptTaskState?: {
|
||||
taskId: ReturnType<typeof ref<string>>;
|
||||
taskStatus: ReturnType<typeof ref<TaskStatus | null>>;
|
||||
};
|
||||
};
|
||||
|
||||
(globalThis as MonacoGlobal).MonacoEnvironment = {
|
||||
@@ -65,13 +77,21 @@ const isModified = ref(false);
|
||||
const loading = ref(false);
|
||||
const saving = ref(false);
|
||||
const executing = ref(false);
|
||||
const taskId = ref('');
|
||||
const taskStatus = ref<TaskStatus | null>(null);
|
||||
let timer: number | undefined;
|
||||
let themeObserver: MutationObserver | undefined;
|
||||
let applyingRemoteContent = false;
|
||||
|
||||
const globalWindow = window as MonacoGlobal;
|
||||
const scriptTaskState = globalWindow.__capacityReportScriptTaskState || {
|
||||
taskId: ref(''),
|
||||
taskStatus: ref<TaskStatus | null>(null)
|
||||
};
|
||||
globalWindow.__capacityReportScriptTaskState = scriptTaskState;
|
||||
const taskId = scriptTaskState.taskId;
|
||||
const taskStatus = scriptTaskState.taskStatus;
|
||||
|
||||
const logText = computed(() => taskStatus.value?.logs?.join('\n') || '等待执行日志');
|
||||
const logLines = computed(() => toColoredLogLines(logText.value, '等待执行日志'));
|
||||
const statusClass = computed(() => {
|
||||
if (taskStatus.value?.status === 'completed') return 'completed';
|
||||
if (taskStatus.value?.status === 'failed') return 'failed';
|
||||
@@ -123,6 +143,9 @@ onMounted(async () => {
|
||||
createEditor();
|
||||
observeTheme();
|
||||
await loadScript();
|
||||
if (taskId.value && taskStatus.value && !['completed', 'failed'].includes(taskStatus.value.status)) {
|
||||
startPolling();
|
||||
}
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
|
||||
@@ -659,9 +659,9 @@ async function changePassword() {
|
||||
async function downloadConfig() {
|
||||
try {
|
||||
const filename = 'Configure.json';
|
||||
const completed = await downloadGet('/api/config/download', filename);
|
||||
if (completed) {
|
||||
showDownloadCompleteDialog(dialog, filename);
|
||||
const result = await downloadGet('/api/config/download', filename);
|
||||
if (result.saved) {
|
||||
showDownloadCompleteDialog(dialog, filename, result.path);
|
||||
}
|
||||
} catch (error) {
|
||||
message.error(error instanceof Error ? error.message : '下载配置失败');
|
||||
|
||||
Reference in New Issue
Block a user