import { importBlob } from '@/api/common'; import { downloadXls } from '@/utils/util'; import { ElLoading } from 'element-plus'; import NProgress from 'nprogress'; import 'nprogress/nprogress.css'; const excelPattern = /\.(xls|xlsx)$/; const isExcelResponse = res => { const contentType = res.headers['content-type'] || res.data.type || ''; return contentType.includes('application/vnd.ms-excel'); }; const parseBlobJson = async blob => { const text = await blob.text(); return text ? JSON.parse(text) : {}; }; /** * 加工后端返回的导入失败明细 Excel。 * 若传入 failDetailDecorator(函数),则动态加载 exceljs 读取 blob 流, * 交由 decorator 完成改写原因列、调整列宽等加工,再导出新的 blob; * 否则原样返回 blob(保持旧行为,不影响未使用 decorator 的模块)。 * @param {Blob} blob 后端返回的失败明细 Excel 流 * @param {String} businessName 业务名称(如「货物类型」) * @param {Function} [failDetailDecorator] async (workbook, { businessName }) => void * @returns {Promise} */ const decorateFailExcel = async (blob, businessName, failDetailDecorator) => { if (typeof failDetailDecorator !== 'function') return blob; const ExcelJS = await import('exceljs'); const buf = await blob.arrayBuffer(); const workbook = new ExcelJS.Workbook(); await workbook.xlsx.load(buf); await failDetailDecorator(workbook, { businessName }); const out = await workbook.xlsx.writeBuffer(); return new Blob([out], { type: 'application/vnd.ms-excel' }); }; /** * 打开导入弹窗 * @param {Object} vm 组件实例 * @param {String} businessName 业务名称,用于失败明细文件命名 * @param {Function} [onSuccess] 导入成功回调 * @param {Object} [options] 扩展项 * @param {Function} [options.failDetailDecorator] 失败明细加工函数,用于对后端返回的失败 Excel 标红/改写原因 */ export const openImportDialog = (vm, businessName, onSuccess, options = {}) => { const { failDetailDecorator } = options; const excelOption = vm.excelOption || vm.excelOptionConfig; const column = vm.findColumn(excelOption?.column || [], 'excelFile'); if (!column) { vm.$message.error('导入配置异常,请稍后重试'); return; } column.httpRequest = (option, uploadColumn) => handleImportExcel(vm, option, uploadColumn, businessName, onSuccess, { failDetailDecorator }); vm.excelBox = true; }; export const handleImportExcel = async (vm, option, column, businessName, onSuccess, options = {}) => { const { failDetailDecorator } = options; const file = option.file; const fileName = file && file.name ? file.name.toLowerCase() : ''; if (!excelPattern.test(fileName)) { vm.$message.error('请上传 .xls,.xlsx 标准格式文件'); if (typeof option.onError === 'function') { option.onError(new Error('请上传 .xls,.xlsx 标准格式文件')); } return; } NProgress.start(); const loading = ElLoading.service({ lock: true, text: '导入中', background: 'rgba(255, 255, 255, 0.7)', }); try { const res = await importBlob(column.action, file); if (isExcelResponse(res)) { const decorated = await decorateFailExcel(res.data, businessName, failDetailDecorator); downloadXls( decorated, `${businessName}导入失败明细${vm.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx` ); vm.$message.warning('部分数据导入失败,已下载失败明细'); } else { const result = await parseBlobJson(res.data); if (result.code !== 200) { vm.$message.error(result.msg || '导入失败'); if (typeof option.onError === 'function') { option.onError(new Error(result.msg || '导入失败')); } return; } vm.$message.success('导入完成'); } vm.excelBox = false; vm.excelForm = {}; if (typeof onSuccess === 'function') { onSuccess(); } else if (typeof vm.onLoad === 'function') { vm.onLoad(vm.page, vm.query); } else if (typeof vm.initTree === 'function') { vm.initTree(); } if (typeof option.onSuccess === 'function') { option.onSuccess(res); } } catch (error) { vm.$message.error(error.message || '导入失败'); if (typeof option.onError === 'function') { option.onError(error); } } finally { loading.close(); NProgress.done(); } };