Files
DashBoard/frontend/src/shared-composables/TraceProgressBar.vue
egg 519f8ae2f4 feat(lineage): unified LineageEngine, EventFetcher, and progressive trace API
Introduce a unified Seed→Lineage→Event pipeline replacing per-page Python
BFS with Oracle CONNECT BY NOCYCLE queries, add staged /api/trace/*
endpoints with rate limiting and L2 Redis caching, and wire progressive
frontend loading via useTraceProgress composable.

Key changes:
- Add LineageEngine (split ancestors / merge sources / full genealogy)
  with QueryBuilder bind-param safety and batched IN clauses
- Add EventFetcher with 6-domain support and L2 Redis cache
- Add trace_routes Blueprint (seed-resolve, lineage, events) with
  profile dispatch, rate limiting, and Redis TTL=300s caching
- Refactor query_tool_service to use LineageEngine and QueryBuilder,
  removing raw string interpolation (SQL injection fix)
- Add rate limits and resolve cache to query_tool_routes
- Integrate useTraceProgress into mid-section-defect with skeleton
  placeholders and fade-in transitions
- Add lineageCache and on-demand lot lineage to query-tool
- Add TraceProgressBar shared component
- Remove legacy query-tool.js static script (3k lines)
- Fix MatrixTable package column truncation (.slice(0,15) removed)
- Archive unified-lineage-engine change, add trace-progressive-ui specs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 16:30:24 +08:00

167 lines
3.1 KiB
Vue

<script setup>
import { computed } from 'vue';
const props = defineProps({
current_stage: {
type: String,
default: null,
},
completed_stages: {
type: Array,
default: () => [],
},
stage_errors: {
type: Object,
default: () => ({}),
},
});
const STAGES = Object.freeze([
{ id: 'seed-resolve', key: 'seed', label: '批次解析' },
{ id: 'lineage', key: 'lineage', label: '血緣追溯' },
{ id: 'events', key: 'events', label: '事件查詢' },
]);
const completedSet = computed(() => new Set(props.completed_stages || []));
function hasStageError(stage) {
const error = props.stage_errors?.[stage.key];
return Boolean(error?.message || error?.code);
}
function stageState(stage) {
if (hasStageError(stage)) {
return 'error';
}
if (completedSet.value.has(stage.id)) {
return 'complete';
}
if (props.current_stage === stage.id) {
return 'active';
}
return 'pending';
}
const firstError = computed(() => {
for (const stage of STAGES) {
const error = props.stage_errors?.[stage.key];
if (error?.message) {
return `[${stage.label}] ${error.message}`;
}
}
return '';
});
</script>
<template>
<div class="trace-progress">
<div class="trace-progress-track">
<div
v-for="(stage, index) in STAGES"
:key="stage.id"
class="trace-progress-step"
:class="`is-${stageState(stage)}`"
>
<div class="trace-progress-dot"></div>
<span class="trace-progress-label">{{ stage.label }}</span>
<div v-if="index < STAGES.length - 1" class="trace-progress-line"></div>
</div>
</div>
<p v-if="firstError" class="trace-progress-error">{{ firstError }}</p>
</div>
</template>
<style scoped>
.trace-progress {
margin: 12px 0 16px;
padding: 12px 14px;
border: 1px solid #dbeafe;
border-radius: 10px;
background: #f8fbff;
}
.trace-progress-track {
display: flex;
gap: 0;
}
.trace-progress-step {
display: flex;
align-items: center;
flex: 1;
min-width: 0;
color: #94a3b8;
}
.trace-progress-dot {
width: 10px;
height: 10px;
border-radius: 999px;
background: currentColor;
flex-shrink: 0;
}
.trace-progress-label {
margin-left: 8px;
font-size: 12px;
font-weight: 600;
white-space: nowrap;
}
.trace-progress-line {
height: 2px;
background: currentColor;
opacity: 0.35;
flex: 1;
margin: 0 10px;
}
.trace-progress-step.is-complete {
color: #16a34a;
}
.trace-progress-step.is-active {
color: #2563eb;
}
.trace-progress-step.is-active .trace-progress-dot {
animation: trace-progress-pulse 1s ease-in-out infinite;
}
.trace-progress-step.is-error {
color: #dc2626;
}
.trace-progress-error {
margin: 10px 0 0;
color: #b91c1c;
font-size: 12px;
}
@keyframes trace-progress-pulse {
0% {
transform: scale(1);
opacity: 1;
}
50% {
transform: scale(1.35);
opacity: 0.6;
}
100% {
transform: scale(1);
opacity: 1;
}
}
@media (max-width: 840px) {
.trace-progress-track {
flex-direction: column;
gap: 8px;
}
.trace-progress-line {
display: none;
}
}
</style>