Pārlūkot izejas kodu

refactor(case): 收敛清洗入口并调整治理进度展示

- 移除智能页「数据清洗与治理」上传入口及残留 hero 卡片
- 删除 cleanFlowSource/autoGovern 链路,清洗统一按治理设置执行
- 治理未开启时清洗进度页不再展示治理步骤与治理文案
- 修复上传页文件选择框顶部虚线边框因小数像素不渲染的问题
cc 5 dienas atpakaļ
vecāks
revīzija
90adcef3bd

+ 0 - 261
ai-electron/frontend/src/ai/views/aiExpert/index.vue

@@ -1,36 +1,5 @@
 <template>
   <div class="ai-page ai-expert">
-    <!-- 数据来源入口(设计稿 hero 区):「上传文件」按历史逻辑跳转上传数据页;「开始分析」=新建对话并绑定默认内置研判方案 -->
-    <section class="ai-expert__hero-grid">
-      <article class="ai-expert__hero">
-        <div class="ai-expert__hero-icon"><Icon icon="mdi:filter-variant" :size="22" /></div>
-        <div class="ai-expert__hero-body">
-          <div class="ai-expert__hero-title">基于清洗结果智能分析</div>
-          <p class="ai-expert__hero-desc">
-            基于当前案件上传数据经模板库自动清洗治理后的结果,通过系统内置的研判方案进行分析。
-          </p>
-        </div>
-        <button class="ai-expert__hero-start" type="button" @click="onCleanAnalysis">
-          开始分析
-          <Icon icon="mdi:arrow-right" :size="15" />
-        </button>
-      </article>
-
-      <article class="ai-expert__hero">
-        <div class="ai-expert__hero-icon"><Icon icon="mdi:upload" :size="22" /></div>
-        <div class="ai-expert__hero-body">
-          <div class="ai-expert__hero-title">数据清洗与治理</div>
-          <p class="ai-expert__hero-desc"
-            >上传案件文件后自动执行清洗与治理,形成规范、可分析的数据,供后续智能研判使用。</p
-          >
-        </div>
-        <button class="ai-expert__hero-start is-secondary" type="button" @click="onUploadFile">
-          上传文件
-          <Icon icon="mdi:arrow-right" :size="15" />
-        </button>
-      </article>
-    </section>
-
     <!-- 内容卡:标题 + 摘要 + 搜索 + 操作 + 方案卡片网格(工具栏合并进卡头) -->
     <section class="ai-expert__panel">
       <div class="ai-expert__panel-head">
@@ -190,13 +159,10 @@
    * (后端过滤,内置在前),页面不再分组,只靠卡片角标区分来源。
    *
    * 操作语义:
-   *  - 「上传文件」入口:按历史逻辑跳转上传数据页(CaseDataUpload,/data/upload 全屏流程首步);
-   *  - 「开始分析」(第二块卡):=新建对话逻辑,预选默认内置研判方案绑定(复用研判方案卡的 chat 事件);
    *  - 开始分析(研判方案卡):向上层 emit(工作台切到对话视图并预选该研判方案,下一条消息创建的会话即绑定它);
    *  - 编辑/删除:仅自建卡片出现,内置的按钮根本不渲染(后端另有双重校验)。
    */
   import { computed, onMounted, ref } from 'vue';
-  import { useRouter } from 'vue-router';
   import { Button, Modal, Spin, Tooltip } from 'ant-design-vue';
   import Icon from '@/core/components/Icon/src/Icon.vue';
   import { useMessage } from '@/core/hooks/web/useMessage';
@@ -217,7 +183,6 @@
   }>();
 
   const { showMessage } = useMessage();
-  const router = useRouter();
 
   const experts = ref<AiExpert[]>([]);
   const loading = ref(false);
@@ -303,24 +268,6 @@
     emit('chat', e);
   }
 
-  /**
-   * 「开始分析」(基于清洗结果):=新建对话的逻辑,用默认内置研判方案绑定——
-   * 复用研判方案卡的 chat 事件(工作台 startDraft 预选该研判方案 → 切对话视图 → 首条消息创建会话即绑定)。
-   * 内置研判方案列表按后端约定在最前,取第一个 builtin 即默认内置研判方案。
-   */
-  function onCleanAnalysis() {
-    const builtin = experts.value.find((e) => e.builtin);
-    if (builtin) emit('chat', builtin);
-  }
-
-  /**
-   * 「上传文件」入口:跳转上传数据页(上传 → 清洗 → 导入全屏流程的首步)。
-   * 带 redirect 来源地址:上传页的「返回」按它回到本界面,view=agent 让工作台恢复到研判方案视图。
-   */
-  function onUploadFile() {
-    router.push({ name: 'CaseDataUpload', query: { redirect: '/workbench?view=agent' } });
-  }
-
   function openCreate() {
     editing.value = null;
     formOpen.value = true;
@@ -366,103 +313,6 @@
     /* 设计稿是灰底白卡:工作台中栏直接给灰色画布,卡片才能像设计稿一样立起来 */
     background: var(--ai-bg-page);
 
-    /* ── 数据来源入口(设计稿 hero 区) ── */
-    &__hero-grid {
-      display: grid;
-      grid-template-columns: 1fr 1fr;
-      gap: var(--ai-sp-3);
-      flex: 0 0 auto;
-    }
-
-    &__hero {
-      position: relative;
-      display: flex;
-      align-items: center;
-      gap: 14px;
-      padding: 18px 20px;
-      overflow: hidden;
-      background: var(--ai-bg-card, #fff);
-      border: 1px solid var(--ai-border);
-      border-radius: var(--ai-radius-lg);
-      box-shadow: var(--ai-shadow-sm);
-      transition:
-        box-shadow 0.15s ease,
-        transform 0.15s ease;
-
-      &:hover {
-        box-shadow: var(--ai-shadow);
-        transform: translateY(-1px);
-      }
-
-      /* 角部淡蓝光晕:只做一点品牌氛围,保持克制 */
-      &::after {
-        content: '';
-        position: absolute;
-        top: -60px;
-        right: -40px;
-        width: 220px;
-        height: 160px;
-        background: radial-gradient(closest-side, fade(@primary-color, 7%), transparent);
-        pointer-events: none;
-      }
-    }
-
-    &__hero-icon {
-      display: flex;
-      align-items: center;
-      justify-content: center;
-      flex: 0 0 auto;
-      width: 46px;
-      height: 46px;
-      color: var(--ai-primary);
-      background: var(--ai-primary-soft);
-      border-radius: 10px;
-    }
-
-    &__hero-body {
-      flex: 1 1 auto;
-      min-width: 0;
-    }
-
-    &__hero-title {
-      font-size: 16px;
-      font-weight: 700;
-      letter-spacing: 0.01em;
-      color: var(--ai-text);
-    }
-
-    &__hero-desc {
-      margin-top: 5px;
-      font-size: 12.5px;
-      line-height: 1.7;
-      color: var(--ai-text-2);
-    }
-
-    &__hero-start {
-      position: relative;
-      z-index: 1;
-      display: inline-flex;
-      align-items: center;
-      gap: 7px;
-      flex: 0 0 auto;
-      padding: 0 16px;
-      height: 36px;
-      font-size: 13px;
-      font-weight: 600;
-      font-family: inherit;
-      color: #fff;
-      background: var(--ai-primary);
-      border: none;
-      border-radius: var(--ai-radius);
-      box-shadow: 0 2px 6px fade(@primary-color, 28%);
-      cursor: pointer;
-      transition: background 0.15s ease;
-
-      &:hover {
-        background: darken(@primary-color, 8%);
-      }
-    }
-
     /* ── 面板(设计稿 panel:白卡承载标题行 + 提示 + 卡片网格) ── */
     &__panel {
       display: flex;
@@ -862,117 +712,6 @@
   }
 
   .ai-expert {
-    &__hero-grid {
-      display: grid;
-      grid-template-columns: repeat(auto-fit, minmax(340px, 1fr));
-      gap: 8px;
-    }
-
-    &__hero {
-      position: relative;
-      display: flex;
-      align-items: center;
-      gap: 14px;
-      padding: 16px 18px;
-      overflow: hidden;
-      background: #fff;
-      border: 1px solid #e7ebf3;
-      border-radius: 14px;
-      box-shadow: 0 1px 2px rgb(16 24 40 / 4%);
-      transition:
-        border-color 0.15s ease,
-        box-shadow 0.15s ease;
-
-      &::after {
-        display: none;
-      }
-
-      &:hover {
-        border-color: #bfcdfb;
-        box-shadow: 0 4px 14px rgb(16 24 40 / 8%);
-        transform: none;
-      }
-    }
-
-    &__hero-icon {
-      display: flex;
-      flex: 0 0 auto;
-      align-items: center;
-      justify-content: center;
-      width: 40px;
-      height: 40px;
-      color: #2a50ec;
-      background: rgb(42 80 236 / 8%);
-      border-radius: 10px;
-    }
-
-    /* 第二个入口是"上传/清洗",用紫色与"开始分析"区分 */
-    &__hero:nth-child(2) &__hero-icon {
-      color: #7c3aed;
-      background: rgb(124 58 237 / 8%);
-    }
-
-    &__hero-body {
-      flex: 1 1 auto;
-      min-width: 0;
-    }
-
-    &__hero-title {
-      color: #111827;
-      font-size: 14px;
-      font-weight: 600;
-      line-height: 1.5;
-      letter-spacing: 0;
-    }
-
-    &__hero-desc {
-      margin-top: 4px;
-      color: #5b6577;
-      font-size: 12px;
-      line-height: 1.7;
-    }
-
-    &__hero-start {
-      position: relative;
-      z-index: 1;
-      display: inline-flex;
-      flex: 0 0 auto;
-      align-items: center;
-      gap: 6px;
-      height: 30px;
-      padding: 0 14px;
-      color: #fff;
-      font-size: 12px;
-      font-weight: 500;
-      font-family: inherit;
-      background: #2a50ec;
-      border: 1px solid #2a50ec;
-      border-radius: 8px;
-      box-shadow: none;
-      cursor: pointer;
-      transition:
-        color 0.15s ease,
-        background-color 0.15s ease,
-        border-color 0.15s ease;
-
-      &:hover {
-        background: #4b6bf0;
-        border-color: #4b6bf0;
-      }
-
-      &.is-secondary {
-        color: #5b6577;
-        background: #fff;
-        border-color: #e7ebf3;
-      }
-
-      &.is-secondary:hover {
-        color: #2a50ec;
-        background: #f8faff;
-        border-color: #bfcdfb;
-      }
-    }
-
     &__card-title {
       position: relative;
       flex: 0 0 auto;

+ 0 - 5
ai-electron/frontend/src/case/api/govern/types/dto.ts

@@ -46,11 +46,6 @@ export interface DoCleanDTO {
   fileInfos: FileInfo[];
   reClean?: 0 | 1;
   fileId?: ApiId | number;
-  /**
-   * 清洗完成后是否强制自动治理(忽略「同时治理」配置)。
-   * 智能入口传 true;自主入口不传,按治理配置执行。
-   */
-  autoGovern?: boolean;
 }
 
 /** 批次阶段:判定 → 交既有清洗 → AI 入库 → 终态 */

+ 33 - 72
ai-electron/frontend/src/case/views/data/cleanProgress.vue

@@ -11,13 +11,7 @@
           </span>
           <div class="page-title-wrap">
             <div class="page-title">{{ isComplete ? '数据清洗结果' : '数据清洗进行中' }}</div>
-            <div class="page-subtitle">
-              {{
-                isComplete
-                  ? '查看本次清洗、入库与治理的执行结果,以及完整任务日志。'
-                  : '实时展示文件清洗、入库与治理的执行进度,便于快速确认任务运行状态。'
-              }}
-            </div>
+            <div class="page-subtitle">{{ pageSubtitle }}</div>
           </div>
         </div>
         <div v-if="!isComplete" class="sse-pill" :class="{ 'is-on': sseConnected }">
@@ -105,7 +99,7 @@
                 </div>
               </aside>
 
-              <section class="run-log" :class="{ 'is-govern': activeStep >= 2 }">
+              <section class="run-log" :class="{ 'is-govern': shouldRunGovernAfterClean && activeStep >= 2 }">
                 <TaskLogList
                   :logs="taskLog"
                   title="处理日志"
@@ -169,10 +163,7 @@
                 </div>
                 <div class="complete-actions">
                   <Button class="complete-log" @click="handleViewLog">查看清洗日志</Button>
-                  <Button v-if="isSmartCleanEntry" type="primary" class="complete-result" @click="handleStartAnalysis">
-                    开始分析
-                  </Button>
-                  <Button v-else type="primary" class="complete-result" @click="handleViewResult">查看结果</Button>
+                  <Button type="primary" class="complete-result" @click="handleViewResult">查看结果</Button>
                 </div>
               </div>
 
@@ -218,7 +209,6 @@
     clearImportProgress,
     clearLegacyTaskLogStorage,
     getActiveImportProgress,
-    getCleanFlowSource,
     getImportProgressEventSource,
     resetImportProgressRuntimeState,
     saveImportProgress,
@@ -270,12 +260,20 @@
   const governFailed = ref(false);
   let taskLogSeq = 0;
   const logModalOpen = ref(false);
-  /** 执行中左侧的步骤说明:activeStep 0/1/2 与之一一对应 */
-  const runSteps = [
-    { title: '读取数据', desc: '解析上传文件与工作表' },
-    { title: '清洗入库', desc: '按模板清洗并写入案件库' },
-    { title: '数据治理', desc: '去重、关系识别与分类树统计' },
-  ];
+  /**
+   * 执行中左侧的步骤说明:activeStep 0/1(开启治理时含 2)与之一一对应。
+   * 「数据治理」只在治理设置开启自动治理时展示;未开启时流程只有「读取数据 → 清洗入库」两步。
+   */
+  const runSteps = computed(() => {
+    const steps = [
+      { title: '读取数据', desc: '解析上传文件与工作表' },
+      { title: '清洗入库', desc: '按模板清洗并写入案件库' },
+    ];
+    if (shouldRunGovernAfterClean.value) {
+      steps.push({ title: '数据治理', desc: '去重、关系识别与分类树统计' });
+    }
+    return steps;
+  });
   let eventSource = getImportProgressEventSource();
 
   /**
@@ -311,8 +309,8 @@
   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 '清洗入库';
+    if (shouldRunGovernAfterClean.value && activeStep.value >= 2) return '数据治理';
+    if (activeStep.value >= 1) return '清洗入库';
     return '读取数据';
   });
   const progressStatusText = computed(() => {
@@ -506,10 +504,7 @@
     logModalOpen.value = true;
   };
 
-  /**
-   * 自主页面发起的清洗,完成出口「查看结果」:跳清洗结果统计页(导入文件列表带数据量/成功失败条数)。
-   * 智能页面发起的清洗对应出口是「开始分析」(见 {@link handleStartAnalysis})。
-   */
+  /** 完成出口「查看结果」:跳清洗结果统计页(导入文件列表带数据量/成功失败条数) */
   const handleViewResult = async () => {
     if (!shouldRunGovernAfterClean.value) {
       createMessage.warning('数据治理已关闭,本次导入未自动治理,可在数据页手动执行');
@@ -517,26 +512,15 @@
     await navigateToImportResult();
   };
 
-  /** 智能页面发起的清洗,完成出口「开始分析」:直接进案件研判(智能工作台)新对话草稿 */
-  const handleStartAnalysis = async () => {
-    if (!shouldRunGovernAfterClean.value) {
-      createMessage.warning('数据治理已关闭,本次导入未自动治理,可在数据页手动执行');
-    }
-    await go({ path: PageEnum.BASE_WORKBENCH, query: { newChat: '1' } }, true);
-  };
-
   const navigateToImportResult = async () => {
     sessionStorage.removeItem(IMPORT_FILE_INFO_STORAGE_KEY);
     sessionStorage.removeItem(RECLEAN_CONTEXT_STORAGE_KEY);
     markAnalysisDataChanged();
     // 落到「导入文件」页签:列表带每个文件的数据量与成功/失败条数,即清洗结果统计。
-    // 页签与清洗来源(自主/智能)经 sessionStorage 一次性传递——目标页被 keep-alive 缓存,
+    // 页签与完成出口标记经 sessionStorage 一次性传递——目标页被 keep-alive 缓存,
     // 激活时机由 onActivated 消费该上下文决定。
     // 跳转**不带任何 URL 参数**:「案件数据」是固定页签,带 query 会让 URL 变脏、也容易把页签身份搞乱。
-    sessionStorage.setItem(
-      CLEAN_RESULT_CONTEXT_KEY,
-      JSON.stringify({ tab: 'files', mode: isSmartCleanEntry.value ? 'smart' : 'manual' }),
-    );
+    sessionStorage.setItem(CLEAN_RESULT_CONTEXT_KEY, JSON.stringify({ tab: 'files', mode: 'manual' }));
     await go({ path: PageEnum.BASE_HOME }, true);
     await nextTick();
     emitter.emit(CASE_DATA_REFRESH_EVENT);
@@ -660,18 +644,8 @@
   const getRequestErrorMessage = (error: any, fallback = '操作失败') =>
     error?.response?.data?.message || error?.message || fallback;
 
-  /**
-   * 是否「智能入口」发起:智能界面进入的上传/清洗流程默认自动治理,忽略治理配置;
-   * 自主入口(含直接打开本页)继续遵守治理配置。
-   */
-  const isSmartFlowEntry = () => getCleanFlowSource() === 'smart';
-
+  /** 是否在清洗完成后自动治理:按「同时治理」配置执行 */
   const loadGovernSwitch = async () => {
-    // 智能入口:默认自动治理,直接忽略治理配置(也不发起配置查询)
-    if (isSmartFlowEntry()) {
-      shouldRunGovernAfterClean.value = true;
-      return;
-    }
     try {
       const list = await getGovernConfList();
       const hasGovernConf = (Array.isArray(list) ? list : []).find(
@@ -1031,16 +1005,14 @@
     persistImportProgress();
     try {
       const reCleanContext = loadReCleanContext();
-      const autoGovern = isSmartFlowEntry();
       await doClean(
         reCleanContext
           ? {
               fileInfos,
               reClean: 1,
               fileId: reCleanContext.fileId,
-              autoGovern,
             }
-          : { fileInfos, autoGovern },
+          : { fileInfos },
       );
       // 清洗任务已发起:文件列表已读入 currentCleanFiles、进度快照由 persistImportProgress 维护,
       // 因此立即清空「已上传文件缓存」与清洗流程中间缓存,避免后续进入流程时复用上一批文件。
@@ -1072,19 +1044,6 @@
   const aiStage = ref<SmartCleanStatusVO | null>(null);
   const aiStageTimeout = ref(false);
 
-  /**
-   * 完成面板第二个按钮按「清洗从哪个页面发起」区分:
-   * - 自主页面(传统壳:案件数据 → 上传数据 → 清洗)→「查看结果」(清洗结果统计页)
-   * - 智能页面(智能工作台:研判方案视图 → 上传文件 → 清洗)→「开始分析」(回工作台新对话)
-   *
-   * 来源由上传页启动清洗流程时写入 sessionStorage(见 importProgressSession.setCleanFlowSource);
-   * 拿不到时退回旧的 fused 判断兜底(仅智能清洗链路会带 fused=1)。
-   */
-  const isSmartCleanEntry = computed(() => {
-    const source = getCleanFlowSource();
-    return source ? source === 'smart' : isFused.value;
-  });
-
   /** 交给 AI 判定的表数 = 全部 sheet - 原本就匹配上模板的 sheet */
   const aiSheetTotal = computed(() =>
     Math.max(0, (aiStage.value?.sheetTotal ?? 0) - (aiStage.value?.matchedCount ?? 0)),
@@ -1133,10 +1092,17 @@
    */
   const isComplete = computed(() => governFailed.value || completionFallback.value || baseComplete.value);
 
+  /** 页面说明文案:治理未开启时不出现「治理」,与左侧步骤条保持一致 */
+  const pageSubtitle = computed(() => {
+    const scope = shouldRunGovernAfterClean.value ? '清洗、入库与治理' : '清洗与入库';
+    return isComplete.value
+      ? `查看本次${scope}的执行结果,以及完整任务日志。`
+      : `实时展示文件${scope}的执行进度,便于快速确认任务运行状态。`;
+  });
+
   /**
    * 完成时提示「另有 N 个表 AI 无法清洗」——只在完成那一刻提醒一次。
-   * 完成后<b>不自动跳转</b>(2026-10-11 需求):跳转由完成面板上的按钮触发,
-   * 自主页面发起 →「查看结果」,智能页面发起 →「开始分析」。
+   * 完成后<b>不自动跳转</b>(2026-10-11 需求):跳转由完成面板上的「查看结果」按钮触发。
    */
   watch(isComplete, (done) => {
     if (!done) return;
@@ -1332,11 +1298,6 @@
     clearLegacyTaskLogStorage();
 
     if (restoreImportProgress()) {
-      // 刷新恢复:智能入口仍强制自动治理,避免旧快照把治理开关带回配置值
-      if (isSmartFlowEntry()) {
-        shouldRunGovernAfterClean.value = true;
-        persistImportProgress();
-      }
       eventSource = getImportProgressEventSource();
       if (!eventSource) {
         connectSse();

+ 2 - 3
ai-electron/frontend/src/case/views/data/cleaning.vue

@@ -1199,9 +1199,8 @@
   };
 
   const handleBack = () => {
-    // 返回上一级:文件上传页;带上来时的来源地址,保证再返回仍能回到原界面(如工作台)
-    const redirect = String(pageQuery.value.redirect || '');
-    go(redirect ? { path: PageEnum.BASE_UPLOAD, query: { redirect } } : PageEnum.BASE_UPLOAD);
+    // 返回上一级:文件上传页
+    go(PageEnum.BASE_UPLOAD);
   };
 
   onMounted(() => {

+ 1 - 3
ai-electron/frontend/src/case/views/data/importList.vue

@@ -56,7 +56,7 @@
   import { FileStatusEnum } from '@/case/types/enum';
   import componentSetting from '@/core/settings/componentSetting';
   import CleanLogModal from './components/CleanLogModal.vue';
-  import { resetImportProgressSession, setCleanFlowSource } from './importProgressSession';
+  import { resetImportProgressSession } from './importProgressSession';
 
   const props = defineProps({
     treeCodes: Array as PropType<string[]>,
@@ -456,8 +456,6 @@
         }),
       );
       resetImportProgressSession();
-      // 重新清洗入口在「导入文件管理」(自主页面),完成面板出口按自主来源给「查看结果」
-      setCleanFlowSource('manual');
       await go({
         path: PageEnum.BASE_CLEANING,
         query: {

+ 0 - 21
ai-electron/frontend/src/case/views/data/importProgressSession.ts

@@ -2,16 +2,6 @@ import { reactive, toRefs } from 'vue';
 import type { FileInfo } from '@/types/api';
 
 const IMPORT_PROGRESS_STORAGE_KEY = 'case_clean_import_progress';
-const CLEAN_FLOW_SOURCE_STORAGE_KEY = 'case_clean_flow_source';
-
-/**
- * 清洗流程发起来源:
- * - manual:自主页面(传统壳,案件数据页「上传数据」进入上传 → 清洗)
- * - smart :智能页面(智能工作台 / 研判方案视图「上传文件」进入上传 → 清洗)
- *
- * 完成面板的第二个按钮按它区分:「自主」给清洗结果、「智能」给开始分析。
- */
-export type CleanFlowSource = 'manual' | 'smart';
 
 export interface ImportProgressSnapshot {
   active: boolean;
@@ -133,17 +123,6 @@ export const resetImportProgressSession = (caseId?: string | number) => {
   clearImportProgress(caseId);
 };
 
-/** 记录本次清洗流程的发起页(上传页跳转清洗/进度页前写入,跨页读取) */
-export const setCleanFlowSource = (source: CleanFlowSource) => {
-  getStorage()?.setItem(CLEAN_FLOW_SOURCE_STORAGE_KEY, source);
-};
-
-/** 读取本次清洗流程的发起页;未记录过(如旧会话/直连进度页)返回 null,由调用方兜底 */
-export const getCleanFlowSource = (): CleanFlowSource | null => {
-  const value = getStorage()?.getItem(CLEAN_FLOW_SOURCE_STORAGE_KEY);
-  return value === 'smart' || value === 'manual' ? value : null;
-};
-
 // ==================== 清洗 / 治理任务日志 ====================
 
 /**

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

@@ -25,10 +25,7 @@
           </button>
         </div>
         <div class="header-actions">
-          <Button v-if="cleanMode === 'smart'" type="primary" class="header-actions__smart" @click="goSmartAnalysis">
-            进行智能分析
-          </Button>
-          <Button v-else-if="cleanMode === 'manual'" class="header-actions__result" @click="activeTab = 'import'">
+          <Button v-if="cleanMode === 'manual'" class="header-actions__result" @click="activeTab = 'import'">
             查看结果
           </Button>
           <Button type="primary" class="header-actions__upload" @click="handleUploadNavigate('single')"
@@ -312,17 +309,13 @@
   // 支持 ?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,仅作 URL 直连兜底):决定展示「进行智能分析」还是「查看结果」 */
-  const readCleanMode = (): '' | 'smart' | 'manual' => {
+  /** 完成出口标记(?mode=manual,仅作 URL 直连兜底):决定展示「查看结果」 */
+  const readCleanMode = (): '' | 'manual' => {
     const mode = new URLSearchParams(window.location.search).get('mode');
-    return mode === 'smart' || mode === 'manual' ? mode : '';
+    return mode === 'manual' ? mode : '';
   };
-  const cleanMode = ref<'' | 'smart' | 'manual'>(readCleanMode());
+  const cleanMode = ref<'' | 'manual'>(readCleanMode());
 
-  /** 智能模式 CTA:去工作台新建对话,绑定默认研判方案进行分析 */
-  const goSmartAnalysis = () => {
-    go({ path: PageEnum.BASE_WORKBENCH, query: { newChat: '1' } });
-  };
   const governConfigVisible = ref(false);
   const governProgressVisible = ref(false);
   const customQueryPopoverOpen = ref(false);
@@ -361,7 +354,7 @@
   onActivated(() => {
     void restoreActiveImportProgress();
     // 页面被 keep-alive 缓存,重新激活时要重选页签。
-    // 清洗进度页「查看结果」跳转不带任何 URL 参数,页签(导入文件)与清洗来源全走 sessionStorage
+    // 清洗进度页「查看结果」跳转不带任何 URL 参数,页签(导入文件)与完成出口标记全走 sessionStorage
     // 一次性上下文;URL 参数只在直连(手输/收藏)时作兜底。
     let ctx: { tab?: string; mode?: string } | null = null;
     try {
@@ -378,7 +371,7 @@
       activeTab.value = tabQuery;
     }
     const modeQuery = (ctx && ctx.mode) || new URLSearchParams(window.location.search).get('mode');
-    cleanMode.value = modeQuery === 'smart' || modeQuery === 'manual' ? modeQuery : '';
+    cleanMode.value = modeQuery === 'manual' ? modeQuery : '';
   });
 
   onDeactivated(() => {

+ 9 - 35
ai-electron/frontend/src/case/views/data/upload.vue

@@ -190,8 +190,7 @@
   import { clearUploadBatch, deletedFileList, preFileUpload, smartClean } from '@/case/api/govern/governApi';
   import { FileStatusEnum } from '@/case/types/enum';
   import CaseFlowTopBar from './components/CaseFlowTopBar.vue';
-  import { resetImportProgressSession, setCleanFlowSource } from './importProgressSession';
-  import type { CleanFlowSource } from './importProgressSession';
+  import { resetImportProgressSession } from './importProgressSession';
   import type { FileInfo } from '@/types/api';
 
   const { prefixCls } = useDesign('case-upload');
@@ -683,30 +682,6 @@
     },
   ];
 
-  /**
-   * 返回目标:优先用跳入时带来的来源地址(redirect,如从工作台研判方案页进来的 /workbench?view=agent),
-   * 回到出发界面;没有来源时保持历史逻辑回案件数据。仅接受站内绝对路径,防开放重定向。
-   */
-  const backTarget = () => {
-    const redirect = String(pageQuery.value.redirect || '');
-    return redirect.startsWith('/') && !redirect.startsWith('//') ? redirect : PageEnum.BASE_HOME;
-  };
-
-  /** 进入流程下一跳时把来源地址透传下去,保证清洗页返回上传页后仍能回到原界面 */
-  const withRedirect = (query: Record<string, string> = {}) => {
-    const redirect = String(pageQuery.value.redirect || '');
-    return redirect ? { ...query, redirect } : query;
-  };
-
-  /**
-   * 本次上传/清洗流程从哪个页面进入:
-   * 智能工作台的「上传文件」入口固定带 redirect=/workbench?...,
-   * 其余入口(案件数据页「上传数据」)都算自主页面。
-   * 完成面板据此决定第二个按钮是「清洗结果」还是「开始分析」。
-   */
-  const resolveCleanFlowSource = (): CleanFlowSource =>
-    /^\/workbench(\/|\?|$)/.test(String(pageQuery.value.redirect || '')) ? 'smart' : 'manual';
-
   const cleanupAndLeave = async () => {
     isAbandoned.value = true;
     allowedToLeave.value = true;
@@ -716,7 +691,7 @@
       createMessage.warning(error?.message || '清理本批次数据失败,可稍后在导入文件管理中删除');
     } finally {
       clearCleanFlowSessionCache();
-      await go(backTarget(), true);
+      await go(PageEnum.BASE_HOME, true);
       emitter.emit(CASE_DATA_REFRESH_EVENT);
     }
   };
@@ -726,7 +701,7 @@
       allowedToLeave.value = true;
       // 返回案件数据:清空已上传文件与清洗流程缓存,避免下次进入上传页还原上一批文件
       clearCleanFlowSessionCache();
-      go(backTarget(), true);
+      go(PageEnum.BASE_HOME, true);
       emitter.emit(CASE_DATA_REFRESH_EVENT);
       return;
     }
@@ -756,13 +731,12 @@
     try {
       const fileInfos = allFileInfos.value as unknown as FileInfo[];
       persistFileInfos(allFileInfos.value);
-      const res = await smartClean({ fileInfos, reClean: 0, autoGovern: resolveCleanFlowSource() === 'smart' });
+      const res = await smartClean({ fileInfos, reClean: 0 });
       if (!res?.batchId) {
         throw new Error('后端未返回批次号');
       }
       allowedToLeave.value = true;
       resetImportProgressSession();
-      setCleanFlowSource(resolveCleanFlowSource());
       go({
         path: PageEnum.BASE_CLEAN_PROGRESS,
         query: { fused: '1', batchId: String(res.batchId) },
@@ -778,8 +752,7 @@
   const handleManualClean = () => {
     if (!hasCleanableSheet.value) return;
     allowedToLeave.value = true;
-    setCleanFlowSource(resolveCleanFlowSource());
-    go({ path: PageEnum.BASE_CLEANING, query: withRedirect() });
+    go({ path: PageEnum.BASE_CLEANING });
   };
 
   /** 行级手动清洗:定位到指定工作表进入手动清洗页 */
@@ -790,8 +763,7 @@
       return;
     }
     allowedToLeave.value = true;
-    setCleanFlowSource(resolveCleanFlowSource());
-    go({ path: PageEnum.BASE_CLEANING, query: withRedirect({ focusKey }) });
+    go({ path: PageEnum.BASE_CLEANING, query: { focusKey } });
   };
 
   /** 本地移除整个文件节点并同步 sessionStorage */
@@ -1257,7 +1229,9 @@
       flex: 0 0 auto;
       min-height: 96px;
       border-radius: 8px;
-      border: 1px dashed #cbd5e1;
+      /* 2px 虚线:1px 虚线在顶部处于小数像素位置(y≈179.578)时,
+         Chrome 会把整段顶部边框舍弃不渲染,表现为顶部边框消失 */
+      border: 2px dashed #cbd5e1;
       background: #fafbfc;
       transition: all 0.2s ease;
       cursor: pointer;

+ 0 - 7
ai-server/src/main/java/com/zsjz/ai/common/model/dm/dto/DoCleanDTO.java

@@ -10,11 +10,4 @@ public class DoCleanDTO {
     private List<FileInfo> fileInfos;
     private Integer reClean;
     private Long fileId;
-    /**
-     * 清洗完成后是否强制自动治理(忽略「同时治理」配置)。
-     *
-     * <p>从智能界面进入的上传/清洗流程默认 true:清洗完成即自动治理;
-     * 自主界面发起时不传该标记,继续遵守治理配置(hasGovern)。
-     */
-    private Boolean autoGovern;
 }

+ 1 - 2
ai-server/src/main/java/com/zsjz/ai/module/aiclean/api/AiSmartCleanController.java

@@ -45,8 +45,7 @@ public class AiSmartCleanController {
         Long caseId = CaseContextHolder.currentCaseId();
         Long userId = CaseContextHolder.currentUserId();
         return Result.succeed(smartCleanService.submit(caseId, userId,
-                dto == null ? null : dto.getFileInfos(),
-                dto != null && Boolean.TRUE.equals(dto.getAutoGovern())));
+                dto == null ? null : dto.getFileInfos()));
     }
 
     /**

+ 4 - 18
ai-server/src/main/java/com/zsjz/ai/module/aiclean/service/AiSmartCleanService.java

@@ -107,16 +107,6 @@ public class AiSmartCleanService {
      * @param fileInfos 前端上传页那棵树(根节点带 children=sheet)
      */
     public SmartCleanVO submit(Long caseId, Long userId, List<FileInfo> fileInfos) {
-        return submit(caseId, userId, fileInfos, false);
-    }
-
-    /**
-     * 受理批次并立即返回,后台跑完整流程。
-     *
-     * @param autoGovern 智能入口为 true:清洗完成后自动治理,忽略「同时治理」配置;
-     *                   自主入口为 false:按治理配置执行
-     */
-    public SmartCleanVO submit(Long caseId, Long userId, List<FileInfo> fileInfos, boolean autoGovern) {
         if (caseId == null) {
             throw new ServerException("未打开案件,无法智能清洗");
         }
@@ -147,7 +137,7 @@ public class AiSmartCleanService {
         // 必须一开始就放同步容器:判定阶段多个虚拟线程会并发 addReview。
         // 若这里放裸 ArrayList,下方 computeIfAbsent 的同步工厂永远不会生效
         reviewItems.put(batchId, java.util.Collections.synchronizedList(new ArrayList<>()));
-        startAsync(() -> CaseContextHolder.runWith(caseId, userId, () -> run(batchId, caseId, usable, autoGovern)));
+        startAsync(() -> CaseContextHolder.runWith(caseId, userId, () -> run(batchId, caseId, usable)));
         return new SmartCleanVO(batchId, AiCleanStageEnum.JUDGING.name(), usable.size(), sheetTotal,
                 matched, Math.max(0, sheetTotal - matched));
     }
@@ -208,7 +198,7 @@ public class AiSmartCleanService {
      * 智能清洗的 doClean 各趟都是静默的(见 {@code dispatchExistingByLevel}),
      * 因此这一步是全链路唯一的终态出口 —— 纯 AI/无既有清洗的批次也靠它让前端完成跳转。
      */
-    private void pushTerminal(Long caseId, Long batchId, boolean autoGovern) {
+    private void pushTerminal(Long caseId, Long batchId) {
         try {
             Long userId = CaseContextHolder.getUserId();
             if (userId == null) {
@@ -297,7 +287,7 @@ public class AiSmartCleanService {
                 sseService.sendSee(userId, new SseDTO(103, 100, "AI 清洗流程完成",
                         new SeeDataDTO(0, 0, vo)));
             }
-            if (autoGovern || governConfService.hasGovern()) {
+            if (governConfService.hasGovern()) {
                 sseService.sendSee(userId, new SseDTO(102, 100, "[数据治理]开始数据治理!",
                         new SeeDataDTO(0, 0)));
             }
@@ -312,10 +302,6 @@ public class AiSmartCleanService {
      * 后台主流程:判定(并发)→ 一次 doClean → AI 自建表入库 → 终态
      */
     void run(Long batchId, Long caseId, List<FileInfo> roots) {
-        run(batchId, caseId, roots, false);
-    }
-
-    void run(Long batchId, Long caseId, List<FileInfo> roots, boolean autoGovern) {
         long start = System.currentTimeMillis();
         Stats st = new Stats();
         try {
@@ -408,7 +394,7 @@ public class AiSmartCleanService {
             stage(batchId, finalStage);
             // 终态收口(全 SSE):统计 → 状态快照 → 治理宣告;纯 AI/无既有清洗的批次
             // 没有 doClean 的完成回调,前端的「完成并跳转统计页」完全依赖这一推
-            pushTerminal(caseId, batchId, autoGovern);
+            pushTerminal(caseId, batchId);
             log.info("智能清洗批次完成: batchId={}, 阶段={}, 判定={} 条, 交既有清洗={} 条, AI 入库={} 条, "
                             + "待人工={} 条, 失败={} 条, 耗时={}ms",
                     batchId, finalStage, pending.size(), toExisting, st.aiLoad.get(),

+ 4 - 5
ai-server/src/main/java/com/zsjz/ai/module/dm/service/DmService.java

@@ -607,7 +607,7 @@ public class DmService {
     }
 
     private TaskCompleteCallback createDefaultCallback(SlowDownProgress slowDownProgress, Long batchId,
-                                                       boolean silent, boolean autoGovern, CountDownLatch latch) {
+                                                       boolean silent, CountDownLatch latch) {
         return (totalTaskCount, completedTaskCount, message) -> {
             log.info("任务进度:已完成{} / {}({}%)",
                     completedTaskCount, totalTaskCount, Math.round((double) completedTaskCount / totalTaskCount * 100));
@@ -651,9 +651,8 @@ public class DmService {
                         // 静默趟:收口消息(终态/关会话/同时治理)由智能清洗批次统一决定时机,
                         // 但事实性清理(全文检索刷盘/临时文件)与统计照常执行
                         log.info("静默趟清洗完成(batchId={}),收口消息留给智能清洗批次", batchId);
-                    } else if (autoGovern || governConfService.hasGovern()) {
-                        // 智能入口(autoGovern=true)忽略治理配置,清洗完成后直接进入治理
-                        log.info(autoGovern ? "智能入口:忽略治理配置,清洗完成后自动治理!" : "已开启同时治理数据!");
+                    } else if (governConfService.hasGovern()) {
+                        log.info("已开启同时治理数据!");
                         sseService.sendSee(new SseDTO(101, slowDownProgress.calculateNextProgress(), "[数据清洗]所有文件清洗完成!", new SeeDataDTO(completedCount, failNum)));
                         sseService.sendSee(new SseDTO(102, slowDownProgress.calculateNextProgress(), "[数据治理]开始数据治理!", new SeeDataDTO(0, 0)));
                     } else {
@@ -727,7 +726,7 @@ public class DmService {
         AtomicInteger completedTaskCount = new AtomicInteger(0);
 
         TaskCompleteCallback callback = createDefaultCallback(slowDownProgress, cleanJobs.getFirst().fileInfo.getBatchId(),
-                silent, Boolean.TRUE.equals(dto.getAutoGovern()), latch);
+                silent, latch);
 
         if (!silent) {
             sseService.sendSee(new SseDTO(101, slowDownProgress.calculateNextProgress(), "[数据清洗]文件读取完成!", new SeeDataDTO(0, 0)));