将案件数据页(index.vue)头部「选择文件」按钮当前弹出的上传弹框(BasicModal),改为跳转一个全屏页面(/data/upload)完成文件上传,并引入批次(batch)生命周期管理:
upload.vue 挂载时前端生成一个 Long 型 batchId(不依赖首次上传响应),本页所有上传文件统一携带该 batchId 上传(后端 preFileUpload 收到非空 batchId 会直接复用)。#2a50ec、白底面板 #e2e8f0 边框 12px 圆角、渐变 #316dff→#4f8bff、页面底色 #f0f2f5、暗色主题适配),UI 复用 index.vue 既有上传样式与 match.vue 全屏页头样式。handleUploadNavigate('single') → openUpload('single') 打开 BasicModal(模板 L141-L261)。<input type="file" multiple webkitdirectory> L175-L190、拖拽区 L192-L215、文件列表+状态/进度 L218-L253、「开始上传并解析」L254-L258。startUpload(L2604-L2665):逐个 preFileUpload(file, onProgress, { batchId })(POST api/dm/preFileUpload multipart)返回 List<FileInfo> 树(根节点 pid=0=文件、children=各 sheet);全部完成后写 sessionStorage('case_clean_file_infos') 并 go(PageEnum.BASE_MATCH)。当前 batchId 只在首次上传成功后从响应里回填,后续沿用——不满足“进页即生成”。id/pid/fileName/sheetName/tableName/fileStatus/dataNum/fileSize/successNum/failNum/batchId/createDateTime/children;fileStatus 含读取失败/格式不正确/读取完成/加密/模板匹配失败/模板匹配成功等。DataRoute.children 注册全屏页(match/cleaning 用 meta.hideMenu/hideTab/currentActiveMenu=BASE_HOME);pageEnum.ts 已有 BASE_MATCH='/data/match'。BasicTable 支持 isTreeTable,children 字段名由 childrenColumnName 指定(默认 'childList',需显式传 'children'),row-key='id'。#2a50ec;成功 #16a34a、失败 #dc2626、文字 #111827/#475569/#64748b、面板 #fff+#e2e8f0 边框+12px 圆角+0 8px 24px rgba(15,23,42,0.04) 阴影;暗色 html[data-theme='dark'](背景 #0f172a/#111827、边框 #1f2937)。preProcessUploadedFile(MultipartFile, Long batchId, Integer reClean)(DmService.java):batchId != null 时直接复用,否则雪花生成;上传文件落盘 PathConst.WORKSPACE/{caseId}/upload/{batchId}/(L173-L198);解析缓存 PreDataListener 写 PathConst.TMP_PATH/{batchId}/{fileId}_{sheetName}.json;解析结果先驻内存,DB file_info 入库发生在后续 doClean 清洗环节,废弃批次通常无 DB 行。cancelClean(batchId)(L552-L559):仅删 TMP_PATH/{batchId} 缓存,不删上传文件、不删 DB 记录。file_info 记录),需新增。文件:e:\workspace\zsjz-ai\ai-server\src\main\java\com\zsjz\ai\module\dm\service\DmService.java
新增方法:
/** 清理废弃批次:删除本批次已上传文件、解析缓存与 file_info 记录(仅用于未进入清洗的批次) */
public void clearUploadBatch(Long batchId) {
if (batchId == null) {
return;
}
// 1) 解析缓存 tmp/{batchId}
try {
FileUtil.del(PathConst.TMP_PATH.resolve(batchId.toString()));
} catch (Exception e) {
log.warn("清理批次解析缓存失败, batchId={}", batchId, e);
}
// 2) 已上传文件 workspace/{caseId}/upload/{batchId}
try {
if (StateManager.instance().isCaseOpened()) {
Long caseId = StateManager.instance().getCaseId().longValue();
FileUtil.del(PathConst.WORKSPACE.resolve(String.valueOf(caseId))
.resolve(UPLOAD_DIR_NAME).resolve(batchId.toString()));
}
} catch (Exception e) {
log.warn("清理批次上传文件失败, batchId={}", batchId, e);
}
// 3) 本批次 file_info 记录(安全兜底;正常废弃批次无记录)
fileInfoMapper.delete(Wrappers.lambdaQuery(FileInfo.class).eq(FileInfo::getBatchId, batchId));
}
文件:e:\workspace\zsjz-ai\ai-server\src\main\java\com\zsjz\ai\module\dm\controller\DmController.java
新增(参照既有 cancelClean GET 风格,幂等):
/** 清理废弃批次:删除本批次已上传文件、解析缓存与记录(上传页放弃离开时调用) */
@GetMapping("/clearUploadBatch")
public Result<Void> clearUploadBatch(Long batchId) {
dmService.clearUploadBatch(batchId);
return Result.succeed();
}
文件:e:\workspace\zsjz-ai\ai-frontend\src\case\api\govern\governApi.ts
新增(放在 cancelClean 附近):
/** 清理废弃批次:删除本批次已上传文件、解析缓存与记录(未进入清洗而离开上传页时调用) */
export const clearUploadBatch = (params: { batchId?: ApiId | number }) =>
defHttp.get<void>({ url: adminPath + '/dm/clearUploadBatch', params });
upload.vue文件:新建 e:\workspace\zsjz-ai\ai-frontend\src\case\views\data\upload.vue(组件命名 useDesign('case-upload'),prefix jeesite-case-upload)
批次生命周期(核心新增逻辑):
挂载时生成批次 id(“进页即生成,交给后端”):不依赖后端响应,本地生成 Long:
const createUploadBatchId = () => Date.now() * 1000000 + Math.floor(Math.random() * 1000000); // 约 1.7e18,Long 安全
startUpload 中每个文件统一传 { batchId: uploadBatchId.value },保证本页全部文件同批(后端对非空 batchId 复用)。删除原“首次响应回填 batchId”逻辑。
isUploading || selectedFiles.length > 0 || resultTrees.length > 0(本页有上传活动),用 Modal.confirm 提示「上传解析进行中/存在未处理文件,离开将清理本批次已上传文件与解析结果,是否继续?」;取消 → 留在本页;确认 → await clearUploadBatch({ batchId })(失败仅 warning 不阻塞)→ 清空 sessionStorage('case_clean_file_infos')(仅当其中结果全属本批次)→ go(PageEnum.BASE_HOME, true) + emitter.emit('case-data:refresh')。onBeforeRouteLeave 守卫兜底浏览器返回/标签页关闭:仅当 allowedToLeave 为 true(点「下一步」或已确认清理后的程序化跳转)时放行,否则中止并弹同类确认。allowedToLeave=true,写 sessionStorage('case_clean_file_infos') + emitter.emit('case-import-file:refresh') + go(PageEnum.BASE_MATCH),不调用清理接口。页面结构:
match.vue):左=LeftOutlined 返回按钮 + 标题「文件上传」+ 副标题「上传并解析 xls / xlsx / csv 文件,解析结果实时展示在右侧」;右上=主按钮「下一步:数据匹配」(无成功解析时 disabled)。index.vue 全出血样式(background:#f0f2f5; width:calc(100% + 24px); margin:0 -12px; padding:12px),内部 display:flex; gap:12px; height:100%; min-height:0。
flex:0 0 460px 白底卡片):.upload-mode-switch 样式,默认取路由 query mode,缺省 single)+ 「选择文件/选择文件夹」主按钮触发隐藏 input;保留拖拽区(复用 .upload-dropzone)。.upload-file-list(复用原样式)文件列表:文件名 + 大小 + 状态徽标(待上传 / 解析中 n% / ✓ / ✗)。.upload-overall-progress + 「开始上传」主按钮(无文件或上传中禁用)+ 「清空」链接。flex:1 白底卡片):标题「解析结果(实时)」+ BasicTable::is-tree-table="true"、:children-column-name="'children'"、row-key="id"、:pagination="false"、:data-source="resultTrees"。fileStatus 映射彩色标签:成功 #16a34a/失败 #dc2626/其他灰)。逻辑(从 index.vue 移植并改造):
uploadMode、uploadBatchId、selectedFiles、uploadStatuses/uploadProgressMap/overallProgress/isUploading/dragOver、resultTrees(实时结果数组)、allowedToLeave。formatFileSize/isValidUploadFile/filterValidFiles/handleDragOver/handleDragLeave/collectFilesFromDataTransfer/traverseDirectory/handleFileInputChange/handleFolderInputChange。startUpload:逐个 preFileUpload(file, onProgress, { batchId: uploadBatchId.value });每份文件返回的 FileInfo[] 追加到 resultTrees(右侧实时刷新)并同步累积写 sessionStorage('case_clean_file_infos');整体进度 = 已完成文件数占比;全部结束显示「下一步」。mapStatus:从 match.vue L185 移植(SUCCESS/COMPLETE→成功、FAIL→失败、PASSWORD/FORMAT/EMPTY→对应文案)。样式:复用 index.vue L5810-L6060 上传样式(upload-mode-switch / upload-dropzone / upload-file-list / upload-file-item / upload-overall-progress),补充双栏布局与右面板表格样式;沿用 L6076 起 html[data-theme='dark'] 暗色覆盖写法。
e:\workspace\zsjz-ai\ai-frontend\src\core\enums\pageEnum.ts:新增 BASE_UPLOAD = '/data/upload'。e:\workspace\zsjz-ai\ai-frontend\src\core\router\routes\index.ts:DataRoute.children 新增:
{
path: 'upload',
name: 'CaseDataUpload',
component: () => import('@/case/views/data/upload.vue'),
meta: { title: '文件上传', hideMenu: true, hideTab: true, currentActiveMenu: PageEnum.BASE_HOME },
},
index.vue:去掉弹框、改为跳转handleUploadNavigate(L920-L926)改为 go({ path: PageEnum.BASE_UPLOAD, query: { mode: mode || 'single' } });删除 openUpload/handleUploadModeChange/handleUploadChange/handleUploadOk/handleUploadCancel。.upload-modal-body 相关样式(L5810-L6060、L6063-L6074 媒体查询)。uploadVisible/uploadMode/uploadBizKey/uploadLoadTime/fileInputRef/folderInputRef/selectedFiles/uploadResults/uploadProgressMap/uploadStatuses/isUploading/overallProgress/dragOver/registerUploadModal、formatFileSize/isValidUploadFile/filterValidFiles/handleDragOver/handleDragLeave/traverseDirectory/collectFilesFromDataTransfer/handleDrop/handleFileInputChange/handleFolderInputChange/handleTriggerFileSelect/clearSelectedFiles/startUpload、VALID_UPLOAD_EXTS。preFileUpload(governApi)、Upload(antd)、BasicModal, useModal、FileInfo 类型。保留 buildUUID(L1604/L1674 仍用)、createMessage、Modal(模板创建弹框用)。batchId 并在每个文件请求中回传;后端非空即复用,无需新增“创建批次”接口。生成值上限约 1.7e18 < Long.MAX,满足 Long 转换。clearUploadBatch;点「下一步」进入 /data/match 是正常流程,不清理。single。childrenColumnName='children'、row-key='id')。index.vue 写法;不动既有 /dm/preFileUpload 接口语义。mvn compile -f pom.xml 编译通过;GET /dm/clearUploadBatch?batchId=xxx 返回成功,且对应 workspace/{caseId}/upload/{batchId}、tmp/{batchId} 与 file_info 记录被清除(清理前后比对目录与 DB)。tools.build_project 或 npm run build),无未使用导入告警。/data/upload 全屏页(不再弹框),进入即生成批次 id(可经网络请求确认所有文件携带同一 batchId)。clearUploadBatch 并返回 /data/index,后端目录与记录被清理;取消则留在本页。/data/match,结果与改动前一致,且不触发清理。