Explorar el Código

!1799 修改导入界面,使其对用户操作更友好:
Merge pull request !1799 from hwjmyz/v2

zuohuaijun hace 1 año
padre
commit
b5dda54881
Se han modificado 1 ficheros con 157 adiciones y 44 borrados
  1. 157 44
      Web/src/components/table/importData.vue

+ 157 - 44
Web/src/components/table/importData.vue

@@ -1,6 +1,6 @@
 <template>
 	<div class="sys-import-data-container">
-		<el-dialog v-model="state.isShowDialog" draggable :close-on-click-modal="false" width="300px">
+		<el-dialog v-model="state.isShowDialog" draggable :close-on-click-modal="false" width="400px" @close="resetDialog">
 			<template #header>
 				<div style="color: #fff">
 					<el-icon size="16" style="margin-right: 3px; display: inline; vertical-align: middle"> <ele-UploadFilled /> </el-icon>
@@ -9,27 +9,47 @@
 			</template>
 			
 			<el-row :gutter="15" v-loading="state.loading">
-				<el-col :xs="12" :sm="12" :md="12" :lg="12" :xl="12">
-					<el-button class="ml10" type="info" icon="ele-Download" v-reclick="3000" @click="() => download()" :disabled="state.loading">模板</el-button>
+				<el-col :span="24" class="mb10">
+					<el-button icon="ele-Download" v-reclick="3000" @click="download" :disabled="state.loading">模板</el-button>
 				</el-col>
-				<el-col :xs="12" :sm="12" :md="12" :lg="12" :xl="12">
+				
+				<el-col :span="24" class="mb15 flex">
 					<el-upload
 						:limit="1"
 						:show-file-list="false"
+						:auto-upload="false"
 						:on-exceed="handleExceed"
-						:http-request="handleImportData"
+						:on-change="handleFileChange"
 						ref="uploadRef"
 					>
 						<template #trigger>
-							<el-button type="primary" icon="ele-MostlyCloudy" v-reclick="3000" :disabled="state.loading">导入</el-button>
+							<el-button class="mr10" type="primary" icon="ele-MostlyCloudy" :disabled="state.isCompleted || state.loading">选择文件</el-button>
 						</template>
 					</el-upload>
+					<span class="selected-file">{{ state.selectedFile ? state.selectedFile.name : '未选择文件' }}</span>
+				</el-col>
+				
+				<!-- 错误提示区域 -->
+				<el-col :span="24" v-if="state.importResultUrl" class="mt10">
+					<div v-if="state.hasError" style="color: red; margin-bottom: 10px;">
+						导入完毕,存在部分错误,请下载导入结果查看详情
+					</div>
+					<el-link type="primary" :underline="false" @click="downloadImportResult">
+						<el-icon class="mr5"><ele-Download /></el-icon>下载导入结果
+					</el-link>
 				</el-col>
 			</el-row>
 
 			<template #footer>
 				<span class="dialog-footer">
-					<el-button @click="() => state.isShowDialog = false" :disabled="state.loading">取 消</el-button>
+					<el-button @click="closeDialog" :disabled="state.loading">取 消</el-button>
+					<el-button 
+						type="primary" 
+						@click="state.isCompleted ? closeDialog() : submitImport()"
+						:disabled="(!state.selectedFile && !state.isCompleted) || state.loading"
+					>
+						{{ state.isCompleted ? '关 闭' : '确 定' }}
+					</el-button>
 				</span>
 			</template>
 		</el-dialog>
@@ -37,15 +57,20 @@
 </template>
 
 <script lang="ts" setup name="sysImportData">
-import type {UploadInstance, UploadProps, UploadRawFile, UploadRequestOptions} from 'element-plus'
+import type { UploadInstance, UploadProps, UploadRawFile, UploadFile } from 'element-plus'
 import { ElUpload, ElMessage, genFileId } from 'element-plus';
 import { downloadStreamFile } from '/@/utils/download';
 import { reactive, ref } from 'vue';
 
 const uploadRef = ref<UploadInstance>();
 const state = reactive({
-	isShowDialog: false,
-	loading: false,
+  isShowDialog: false,
+  loading: false,
+  isCompleted: false,
+  hasError: false,
+  selectedFile: null as File | null,
+  importResultUrl: '' as string,
+  importResultName: '' as string
 });
 
 // 定义子组件向父组件传值/事件
@@ -54,7 +79,26 @@ const emit = defineEmits(['refresh']);
 
 // 打开弹窗
 const openDialog = () => {
-	state.isShowDialog = true;
+  resetDialog();
+  state.isShowDialog = true;
+};
+
+// 重置对话框状态
+const resetDialog = () => {
+  state.isCompleted = false;
+  state.hasError = false;
+  state.selectedFile = null;
+  state.importResultUrl = '';
+  state.importResultName = '';
+  uploadRef.value?.clearFiles();
+};
+
+// 关闭弹窗
+const closeDialog = () => {
+  state.isShowDialog = false;
+  if (state.importResultUrl) {
+    URL.revokeObjectURL(state.importResultUrl);
+  }
 };
 
 // 选择文件超出上限事件
@@ -65,44 +109,113 @@ const handleExceed: UploadProps['onExceed'] = (files) => {
   uploadRef.value!.handleStart(file);
 }
 
-// 数据导入
-const handleImportData = (opt: UploadRequestOptions): any => {
-  state.loading = true;
-  props.import(opt.file).then((res: any) => {
-    // 返回json数据的情况
-	const contentType = res.headers['content-type'];
-	if (contentType && contentType.toLowerCase().includes('application/json')) {
-		const decoder = new TextDecoder('utf-8');
-		const data = decoder.decode(res.data);
-		try {
-			const result = JSON.parse(data);
-			if(result.code == '200'){
-				ElMessage.success(result.message);
-			} else {
-				ElMessage.error(result.message);
-				return;
-			}
-		} catch (e) {
-			console.error("解析数据导入结果失败:", e);
-			downloadStreamFile(res);
-		}
-	}
-	else {
-		downloadStreamFile(res);
-	}
-    emit('refresh');
-    state.isShowDialog = false;
-  }).finally(() => {
-    uploadRef.value?.clearFiles();
+// 文件选择变更事件
+const handleFileChange = (file: UploadFile) => {
+  state.selectedFile = file.raw as File;
+};
+
+// 提交导入
+const submitImport = async () => {
+  if (!state.selectedFile) return;
+  
+  try {
+    state.loading = true;
+    const res = await props.import(state.selectedFile);
+    
+    // 处理导入结果
+    const contentType = res.headers['content-type'] || '';
+    if (contentType.includes('application/json')) {
+      // JSON响应处理(无错误文件)
+      const decoder = new TextDecoder('utf-8');
+      const data = decoder.decode(res.data);
+      const result = JSON.parse(data);
+      
+      if (result.code === 200) {
+        ElMessage.success(result.message);
+        emit('refresh');
+        state.hasError = false;
+        closeDialog();  // 关键修改:成功导入后直接关闭对话框
+      } else {
+        ElMessage.error(result.message);
+        state.hasError = false;
+      }
+    } else {
+      // 二进制响应处理(有错误文件)
+      const blob = new Blob([res.data]);
+      const contentDisposition = res.headers['content-disposition'];
+      let filename = '导入结果.xlsx';
+      
+      if (contentDisposition) {
+        const match = contentDisposition.match(/filename="?([^"]+)"?/);
+        if (match && match[1]) {
+          filename = decodeURIComponent(match[1]);
+        }
+      }
+      
+      // 清除旧URL
+      if (state.importResultUrl) {
+        URL.revokeObjectURL(state.importResultUrl);
+      }
+      
+      // 创建导入结果URL
+      state.importResultUrl = URL.createObjectURL(blob);
+      state.importResultName = filename;
+      state.isCompleted = true;
+      state.hasError = true;
+      
+	  //刷新列表显示
+	  emit('refresh');
+
+      ElMessage.warning('导入完成,存在部分错误');
+    }
+  } catch (error) {
+    console.error('导入错误:', error);
+    ElMessage.error('导入过程中发生错误');
+    state.hasError = false;
+  } finally {
     state.loading = false;
-  });
-}
+  }
+};
+
+// 下载导入结果
+const downloadImportResult = () => {
+  if (!state.importResultUrl) return;
+  
+  const link = document.createElement('a');
+  link.href = state.importResultUrl;
+  link.download = state.importResultName;
+  document.body.appendChild(link);
+  link.click();
+  document.body.removeChild(link);
+  
+  // 关闭对话框(可选,根据需求决定是否保留)
+  // closeDialog();
+};
 
 // 下载模板
 const download = () => {
-	props.download().then((res: any) => downloadStreamFile(res)).catch((res: any) => ElMessage.error('下载错误: ' + res));
+  props.download()
+    .then((res: any) => downloadStreamFile(res))
+    .catch((err: any) => ElMessage.error('下载错误: ' + err));
 }
 
 // 导出对象
-defineExpose({ openDialog });
+defineExpose({ openDialog, closeDialog });
 </script>
+
+<style scoped>
+.selected-file {
+  margin-left: 10px;
+  line-height: 32px;
+  color: var(--el-text-color-regular);
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+  max-width: 200px;
+}
+
+.flex {
+  display: flex;
+  align-items: center;
+}
+</style>