Complete frontend console redesign

This commit is contained in:
2026-07-19 19:49:05 +08:00
parent 1b1d983060
commit 12b7e31783
7 changed files with 1097 additions and 214 deletions
+63 -9
View File
@@ -115,7 +115,7 @@
:disabled="!canStart"
@click="startIngest"
>
<app-icon name="play" :size="16" filled />
<app-icon name="play" :size="16" :filled="true" />
<span>执行一键入库</span>
</button>
<button
@@ -288,14 +288,21 @@
import { computed, nextTick, onMounted, onUnmounted, reactive, ref } from 'vue';
import { ElMessage } from 'element-plus';
import AppIcon from './common/AppIcon.vue';
import { getConfig } from '../api/config';
import { startIngest as apiStartIngest, getIngestStatus, getIngestCurrentStatus } from '../api/ingest';
import type { IngestStatusResponse } from '../api/ingest';
import { useWebSocket } from '../composables/useWebSocket';
import type { ProgressMessage } from '../composables/useWebSocket';
import { useAppState } from '../composables/useAppState';
const { state: appState, isConfigured, refreshConfig, setIngestRunning, setIngestPercentage } = useAppState();
const {
state: appState,
isConfigured,
refreshConfig,
setIngestRunning,
setIngestPercentage,
claimIngestViewOwnership,
releaseIngestViewOwnership
} = useAppState();
const submitting = ref(false);
const configLoading = ref(false);
@@ -330,10 +337,12 @@ const percentage = computed(() => {
return Math.min(100, Math.round((progress.processed / progress.total) * 100));
});
/** 被拒绝类别之和(不含已入库)。 */
/**
* 被拒绝类别之和(不含已入库、不含重复跳过)。
* 重复文件是设计上的跳过,不计入拒绝/部分失败。
*/
const totalRejected = computed(() =>
(progress.duplicateFiles ?? 0)
+ (progress.missingMetadataFiles ?? 0)
(progress.missingMetadataFiles ?? 0)
+ (progress.unreadableFiles ?? 0)
+ (progress.conversionFailedFiles ?? 0)
+ (progress.otherRejectedFiles ?? 0)
@@ -393,7 +402,11 @@ const resultText = computed(() => {
if (totalRejected.value > 0) {
return `导入完成:成功入库 ${progress.ingestedFiles ?? 0} 首,${totalRejected.value} 首被拒绝`;
}
return `导入完成:全部 ${progress.processed ?? 0} 首成功入库`;
const dups = progress.duplicateFiles ?? 0;
if (dups > 0) {
return `导入完成:成功入库 ${progress.ingestedFiles ?? 0} 首,${dups} 首重复跳过`;
}
return `导入完成:全部 ${progress.ingestedFiles ?? progress.processed ?? 0} 首成功入库`;
});
/* ---------------- 活动记录(仅由真实状态转移构建) ---------------- */
@@ -439,9 +452,20 @@ function classifyLevel(msg: ProgressMessage): { level: ActivityLevel; badge: str
if (text.includes('损坏') || text.includes('不可读') || text.includes('转码失败')) {
return { level: 'error', badge: '[错误]' };
}
// 重复/跳过按设计是警告级事件记录,但不计入完成态「拒绝」统计。
if (text.includes('重复') || text.includes('跳过') || text.includes('缺') || text.includes('拒绝')) {
return { level: 'warning', badge: '[警告]' };
}
if (msg.completed) {
// 完成分类仅看真实拒绝类别,不含 duplicateFiles。
const rejected =
(msg.missingMetadataFiles ?? 0)
+ (msg.unreadableFiles ?? 0)
+ (msg.conversionFailedFiles ?? 0)
+ (msg.otherRejectedFiles ?? 0);
if (rejected > 0) return { level: 'warning', badge: '[部分]' };
return { level: 'normal', badge: '[完成]' };
}
return { level: 'normal', badge: '[进行]' };
}
@@ -678,6 +702,9 @@ async function copyPath(text: string) {
/* ---------------- 生命周期 ---------------- */
onMounted(async () => {
// 接管细粒度进度源,暂停外壳后台监视(避免双重 HTTP 轮询)。
claimIngestViewOwnership();
// 若外壳尚未加载配置,则此处兜底加载真值。
if (!appState.config.attempted) {
await reloadConfig();
@@ -688,8 +715,8 @@ onMounted(async () => {
onUnmounted(() => {
wsDisconnect();
stopPolling();
// 组件卸载不代表任务停止,但本视图不再持有运行指示
setIngestRunning(false);
// 交还所有权:若任务仍在跑,由外壳后台监视继续同步真实完成态
releaseIngestViewOwnership(submitting.value);
});
</script>
@@ -1476,5 +1503,32 @@ onUnmounted(() => {
.stats-secondary {
grid-template-columns: 1fr;
}
.console-head {
flex-direction: column;
align-items: flex-start;
}
.filter-group {
flex-wrap: wrap;
}
}
@media (max-width: 390px) {
.ingest-view {
gap: 14px;
}
.panel {
padding: 14px;
}
.stat-num {
font-size: 16px;
}
.log-line {
flex-wrap: wrap;
}
}
</style>