importData.vue 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. <template>
  2. <div class="sys-import-data-container">
  3. <el-dialog v-model="state.isShowDialog" draggable :close-on-click-modal="false" width="400px" @close="resetDialog">
  4. <template #header>
  5. <div style="color: #fff">
  6. <el-icon size="16" style="margin-right: 3px; display: inline; vertical-align: middle"> <ele-UploadFilled /> </el-icon>
  7. <span> 数据导入 </span>
  8. </div>
  9. </template>
  10. <el-row :gutter="15" v-loading="state.loading">
  11. <el-col :span="24" class="mb10">
  12. <el-button icon="ele-Download" v-reclick="3000" @click="download" :disabled="state.loading">模板</el-button>
  13. </el-col>
  14. <el-col :span="24" class="mb15 flex">
  15. <el-upload
  16. :limit="1"
  17. :show-file-list="false"
  18. :auto-upload="false"
  19. :on-exceed="handleExceed"
  20. :on-change="handleFileChange"
  21. ref="uploadRef"
  22. >
  23. <template #trigger>
  24. <el-button class="mr10" type="primary" icon="ele-MostlyCloudy" :disabled="state.isCompleted || state.loading">选择文件</el-button>
  25. </template>
  26. </el-upload>
  27. <span class="selected-file">{{ state.selectedFile ? state.selectedFile.name : '未选择文件' }}</span>
  28. </el-col>
  29. <!-- 错误提示区域 -->
  30. <el-col :span="24" v-if="state.importResultUrl" class="mt10">
  31. <div v-if="state.hasError" style="color: red; margin-bottom: 10px;">
  32. 导入完毕,存在部分错误,请下载导入结果查看详情
  33. </div>
  34. <el-link type="primary" :underline="false" @click="downloadImportResult">
  35. <el-icon class="mr5"><ele-Download /></el-icon>下载导入结果
  36. </el-link>
  37. </el-col>
  38. </el-row>
  39. <template #footer>
  40. <span class="dialog-footer">
  41. <el-button @click="closeDialog" :disabled="state.loading">取 消</el-button>
  42. <el-button
  43. type="primary"
  44. @click="state.isCompleted ? closeDialog() : submitImport()"
  45. :disabled="(!state.selectedFile && !state.isCompleted) || state.loading"
  46. >
  47. {{ state.isCompleted ? '关 闭' : '确 定' }}
  48. </el-button>
  49. </span>
  50. </template>
  51. </el-dialog>
  52. </div>
  53. </template>
  54. <script lang="ts" setup name="sysImportData">
  55. import type { UploadInstance, UploadProps, UploadRawFile, UploadFile } from 'element-plus'
  56. import { ElUpload, ElMessage, genFileId } from 'element-plus';
  57. import { downloadStreamFile } from '/@/utils/download';
  58. import { reactive, ref } from 'vue';
  59. const uploadRef = ref<UploadInstance>();
  60. const state = reactive({
  61. isShowDialog: false,
  62. loading: false,
  63. isCompleted: false,
  64. hasError: false,
  65. selectedFile: null as File | null,
  66. importResultUrl: '' as string,
  67. importResultName: '' as string
  68. });
  69. // 定义子组件向父组件传值/事件
  70. const props = defineProps(['import', 'download']);
  71. const emit = defineEmits(['refresh']);
  72. // 打开弹窗
  73. const openDialog = () => {
  74. resetDialog();
  75. state.isShowDialog = true;
  76. };
  77. // 重置对话框状态
  78. const resetDialog = () => {
  79. state.isCompleted = false;
  80. state.hasError = false;
  81. state.selectedFile = null;
  82. state.importResultUrl = '';
  83. state.importResultName = '';
  84. uploadRef.value?.clearFiles();
  85. };
  86. // 关闭弹窗
  87. const closeDialog = () => {
  88. state.isShowDialog = false;
  89. if (state.importResultUrl) {
  90. URL.revokeObjectURL(state.importResultUrl);
  91. }
  92. };
  93. // 选择文件超出上限事件
  94. const handleExceed: UploadProps['onExceed'] = (files) => {
  95. uploadRef.value!.clearFiles();
  96. const file = files[0] as UploadRawFile;
  97. file.uid = genFileId();
  98. uploadRef.value!.handleStart(file);
  99. }
  100. // 文件选择变更事件
  101. const handleFileChange = (file: UploadFile) => {
  102. state.selectedFile = file.raw as File;
  103. };
  104. // 提交导入
  105. const submitImport = async () => {
  106. if (!state.selectedFile) return;
  107. try {
  108. state.loading = true;
  109. const res = await props.import(state.selectedFile);
  110. // 处理导入结果
  111. const contentType = res.headers['content-type'] || '';
  112. if (contentType.includes('application/json')) {
  113. // JSON响应处理(无错误文件)
  114. const decoder = new TextDecoder('utf-8');
  115. const data = decoder.decode(res.data);
  116. const result = JSON.parse(data);
  117. if (result.code === 200) {
  118. ElMessage.success(result.message);
  119. emit('refresh');
  120. state.hasError = false;
  121. closeDialog(); // 关键修改:成功导入后直接关闭对话框
  122. } else {
  123. ElMessage.error(result.message);
  124. state.hasError = false;
  125. }
  126. } else {
  127. // 二进制响应处理(有错误文件)
  128. const blob = new Blob([res.data]);
  129. const contentDisposition = res.headers['content-disposition'];
  130. let filename = '导入结果.xlsx';
  131. if (contentDisposition) {
  132. const match = contentDisposition.match(/filename="?([^"]+)"?/);
  133. if (match && match[1]) {
  134. filename = decodeURIComponent(match[1]);
  135. }
  136. }
  137. // 清除旧URL
  138. if (state.importResultUrl) {
  139. URL.revokeObjectURL(state.importResultUrl);
  140. }
  141. // 创建导入结果URL
  142. state.importResultUrl = URL.createObjectURL(blob);
  143. state.importResultName = filename;
  144. state.isCompleted = true;
  145. state.hasError = true;
  146. //刷新列表显示
  147. emit('refresh');
  148. ElMessage.warning('导入完成,存在部分错误');
  149. }
  150. } catch (error) {
  151. console.error('导入错误:', error);
  152. ElMessage.error('导入过程中发生错误');
  153. state.hasError = false;
  154. } finally {
  155. state.loading = false;
  156. }
  157. };
  158. // 下载导入结果
  159. const downloadImportResult = () => {
  160. if (!state.importResultUrl) return;
  161. const link = document.createElement('a');
  162. link.href = state.importResultUrl;
  163. link.download = state.importResultName;
  164. document.body.appendChild(link);
  165. link.click();
  166. document.body.removeChild(link);
  167. // 关闭对话框(可选,根据需求决定是否保留)
  168. // closeDialog();
  169. };
  170. // 下载模板
  171. const download = () => {
  172. props.download()
  173. .then((res: any) => downloadStreamFile(res))
  174. .catch((err: any) => ElMessage.error('下载错误: ' + err));
  175. }
  176. // 导出对象
  177. defineExpose({ openDialog, closeDialog });
  178. </script>
  179. <style scoped>
  180. .selected-file {
  181. margin-left: 10px;
  182. line-height: 32px;
  183. color: var(--el-text-color-regular);
  184. overflow: hidden;
  185. text-overflow: ellipsis;
  186. white-space: nowrap;
  187. max-width: 200px;
  188. }
  189. .flex {
  190. display: flex;
  191. align-items: center;
  192. }
  193. </style>