Преглед на файлове

fix: 修复清洗进度环空白与案件数据重复页签

- 进度环按真实周长(2πr)计算 dasharray/dashoffset,修复 50% 时进度颜色整段空白
- 「查看结果」跳转不再携带 tab/mode 查询参数,页签与来源继续经 sessionStorage 上下文传递
- 案件数据固定页签按基础路径去重,避免查看结果时重复开出同名页签
cc преди 3 дни
родител
ревизия
a5879f0905

+ 17 - 8
ai-electron/frontend/src/case/views/data/cleanProgress.vue

@@ -65,7 +65,10 @@
                   cy="60"
                   r="50"
                   class="ring-value"
-                  :style="{ strokeDasharray: `${progressDash} 314`, strokeDashoffset: `${314 - progressDash}` }"
+                  :style="{
+                    strokeDasharray: `${RING_CIRCUMFERENCE}`,
+                    strokeDashoffset: `${RING_CIRCUMFERENCE - progressDash}`,
+                  }"
                 />
               </svg>
               <div class="progress-value">
@@ -237,7 +240,14 @@
     sseConnected.value = false;
   };
 
-  const progressDash = computed(() => Math.round((overallProgress.value / 100) * 314));
+  /**
+   * 进度环几何:r=50 → 周长 2πr≈314.159。
+   * dasharray 必须固定为「整圈」,只滚动 dashoffset 才能得到与百分比一致的弧长;
+   * 旧写法 `dasharray: `${dash} 314`` + `offset: 314 - dash` 会让弧长与进度错位
+   * (50% 时弧长为 0 → 圆环上的进度颜色整段空白)。
+   */
+  const RING_CIRCUMFERENCE = 2 * Math.PI * 50;
+  const progressDash = computed(() => (overallProgress.value / 100) * RING_CIRCUMFERENCE);
   const currentStepLabel = computed(() => {
     if (activeStep.value >= 2) return '数据治理';
     if (activeStep.value === 1) return '清洗入库';
@@ -403,15 +413,13 @@
     markAnalysisDataChanged();
     // 落到「导入文件」页签:列表带每个文件的数据量与成功/失败条数,即清洗结果统计。
     // 页签与清洗来源(自主/智能)经 sessionStorage 一次性传递——目标页被 keep-alive 缓存,
-    // 激活时机与 URL 更新存在竞态(URL 参数保留作直连兜底)。
+    // 激活时机由 onActivated 消费该上下文决定。
+    // 跳转**不带任何 URL 参数**:「案件数据」是固定页签,带 query 会让 URL 变脏、也容易把页签身份搞乱。
     sessionStorage.setItem(
       CLEAN_RESULT_CONTEXT_KEY,
       JSON.stringify({ tab: 'files', mode: isSmartCleanEntry.value ? 'smart' : 'manual' }),
     );
-    await go(
-      { path: PageEnum.BASE_HOME, query: { tab: 'files', mode: isSmartCleanEntry.value ? 'smart' : 'manual' } },
-      true,
-    );
+    await go({ path: PageEnum.BASE_HOME }, true);
     await nextTick();
     emitter.emit(CASE_DATA_REFRESH_EVENT);
   };
@@ -1467,7 +1475,8 @@
 
     .ring-bg {
       fill: none;
-      stroke: #eef2f7;
+      // 轨道色不能太接近卡片白底(旧值 #eef2f7 在 0% 时几乎看不见「圆环」存在)
+      stroke: #e2e8f0;
       stroke-width: 10;
     }
 

+ 5 - 5
ai-electron/frontend/src/case/views/data/index.vue

@@ -309,10 +309,10 @@
   }
 
   type DataPageTabKey = 'import' | 'files';
-  // 支持 ?tab=files 直达「导入文件」页签(智能清洗完成后跳转统计页用)
+  // 支持 ?tab=files 直达「导入文件」页签(仅作 URL 直连兜底;正常流程由清洗进度页经 sessionStorage 切换)
   const initialTab = new URLSearchParams(window.location.search).get('tab') === 'files' ? 'files' : 'import';
   const activeTab = ref<DataPageTabKey>(initialTab as DataPageTabKey);
-  /** 清洗来源模式(?mode=smart|manual,由清洗进度页跳转携带):决定展示「进行智能分析」还是「查看结果」 */
+  /** 清洗来源模式(?mode=smart|manual,仅作 URL 直连兜底):决定展示「进行智能分析」还是「查看结果」 */
   const readCleanMode = (): '' | 'smart' | 'manual' => {
     const mode = new URLSearchParams(window.location.search).get('mode');
     return mode === 'smart' || mode === 'manual' ? mode : '';
@@ -360,9 +360,9 @@
 
   onActivated(() => {
     void restoreActiveImportProgress();
-    // 页面被 keep-alive 缓存,重进时要按路由参数重选页签
-    // (智能清洗完成后跳 ?tab=files,直接落到「导入文件」统计列表)
-    // 优先读清洗进度页带来的一次性上下文(页签 + 清洗来源),URL 参数作兜底
+    // 页面被 keep-alive 缓存,重新激活时要重选页签。
+    // 清洗进度页「查看结果」跳转不带任何 URL 参数,页签(导入文件)与清洗来源全走 sessionStorage
+    // 一次性上下文;URL 参数只在直连(手输/收藏)时作兜底。
     let ctx: { tab?: string; mode?: string } | null = null;
     try {
       const raw = sessionStorage.getItem('case_clean_result_context');

+ 8 - 2
ai-electron/frontend/src/core/store/modules/multipleTab.ts

@@ -35,7 +35,10 @@ const resolveTabBasePath = (tab: Partial<RouteLocationNormalized>) => resolveTab
 const resolveTabMatchKey = (tab: Partial<RouteLocationNormalized>) => {
   const fullPath = resolveTabFullPath(tab);
   const basePath = resolveTabBasePath(tab);
-  return basePath === BEHAVIOR_SPACETIME_TAB_PATH ? basePath : fullPath;
+  // 固定页签(案件数据 / 行为时空分析)只按基础路径去重:带 query 进入同一页面时复用原页签,
+  // 不能因为 `?tab=files&mode=manual` 这类参数多开一个「案件数据」签;
+  // 其余页面仍按 fullPath 匹配,保留同页面不同参数各自开签的能力。
+  return basePath === BEHAVIOR_SPACETIME_TAB_PATH || basePath === HOME_TAB_PATH ? basePath : fullPath;
 };
 const dedupeTabsKeepLast = (tabList: RouteLocationNormalized[] = []) => {
   const uniqueTabs: RouteLocationNormalized[] = [];
@@ -69,7 +72,10 @@ const ensureHomeTabPinned = (tabList: RouteLocationNormalized[] = []): RouteLoca
   const homeTab = homeIndex === -1 ? createHomeTab() : nextTabs.splice(homeIndex, 1)[0];
 
   homeTab.path = HOME_TAB_PATH;
-  homeTab.fullPath = HOME_TAB_PATH;
+  // fullPath 不能强制回写为基础路径:页签激活态按 fullPath 与当前路由对齐,
+  // 若这里抹掉 `?tab=files&mode=manual` 之类的 query,路由停在该地址时页签会匹配不到
+  // (表现为页签不高亮、内容区空白)。固定的是「页签身份」,不是 URL 参数。
+  homeTab.fullPath = resolveTabFullPath(homeTab) || HOME_TAB_PATH;
   homeTab.meta = {
     ...(homeTab.meta || {}),
     title: HOME_TAB_TITLE,