excel导入标准化

excel导入较导出还是复杂一些,一般分为三个步骤.市面上低代码平台可以将常用的操作固化,并且形成收益,这也是挺好的。我将我的一些总结分享到网上也是我自己乐意的。毕竟尊重技术的还是搞技术的自身,一般企业老板并不太关心技术代码到底有什么价值,认为脱离了业务代码,这些代码就像封存多年“宝物”上的灰尘。
1下载导入模板
先定义一个对话框,下载导入的excel模板
1

<template><BaseDialog ref="dialog" title="导入其他入库单" :visible='visible' @close="cancelDialogs" @confirm='importConfirm' :confirm-loading='isdeling'  cancelText='取消' confirmText='导入'><p class="firstStep">第一步:请点击下面的链接下载Excel模板,并填写销售出库单信息</p>  <p class="upload" @click="upload()">下载模板</p><p>第二步:导入完成的Excel文件</p><el-upload class="upload-demo" ref="upload" accept=".xls,.xlsx" action="#" :on-change="handleChange" :before-upload='beforeUpload' :on-preview="handlePreview" :on-remove="handleRemove" :file-list="fileList" :auto-upload="false" :http-request="httpRequest"><span slot="trigger" class="upload">选取文件</span><!-- :action="UploadUrl()" --></el-upload></BaseDialog></template><script>import BaseDialog from '@/components/base/BaseDialog.vue';import {computed } from 'vue'import { useAppStore } from '@/store'const appStore = useAppStore()const userInfo = computed(() => appStore.userInfo);export default {props: {// period: {//   type: String,// },visible: {type: Boolean,},cancelDialog: {type: Function,default: () => { },},initTable:{type: Function,default: () => { },}},components:{BaseDialog},data() {return {fileList: [],fileData: '',isdeling: false,tableData: [],status:  false,message : '请选择上传文件',};},methods: {httpRequest(param) {console.log(param.file);},handleChange(file, fileList){this.fileList = fileList.slice(-1);  //限定上传文件为一个},handleRemove(file, fileList) {this.fileData = '';this.status = false;this.message = '请选择上传文件';},beforeUpload(file, fileList) {let testFile = file.name.substring(file.name.lastIndexOf('.')+1)const extension = testFile === 'xls'const extension2 = testFile === 'xlsx'const isLt1M = file.size / 1024 / 1024 < 1;if(!extension && !extension2) {this.message = '上传文件只能是xls或xlsx格式!'this.status = false;}else if (!isLt1M) {this.message = '上传Excel文件大小不能超过 1MB!'this.status = false;}else{let fd = new FormData()fd.append('file', file)fd.append('userId',userInfo.value.id)fd.append('asId', userInfo.value.currentAsId)// fd.append('period', this.period)this.fileData = fd;this.status = true;}},handlePreview(file) {console.log(file);},cancelDialogs(){this.cancelDialog();this.fileData = '';this.status = false;this.message = '请选择上传文件';},async importConfirm() {this.tableData = [];this.$refs.upload.submit()if (this.fileData && this.fileData != ''&& this.status) {this.isdeling = true;// 导入这里有点差别,post请求获取的导入后的结果const re = await this.$api.invOrder.otherInstockOrder.imp(this.fileData)const res = re.dataif (res.success) {this.cancelDialog();this.initTable();this.isdeling = false;this.fileList = [];this.fileData = '';this.status = false;this.message = '请选择上传文件';var reason = ''if ( res.data.failNum > 0 ) {var i = 0;res.data.failReason.forEach(each => {i++;if (i < 5) {reason = reason.concat(`<p style="padding-top:0.5rem">第${each.index}${each.reason},</p>`)}if (i == 5) {reason = reason.concat(`<p style="padding-top:0.5rem">...</p>`)}this.tableData.push({错误行: "第" + each.index + "行",错误信息: each.reason});})//有错误信息将错误信息通过excel导出if (this.tableData.length > 0) {this.$tool.json2Excel(this.tableData)}}let msg = `<p>总条数:${res.data.totalNum} , 成功条数:${res.data.successNum} , 失败条数:${res.data.failNum}</p>${reason}`this.$message.success({duration: 3000,dangerouslyUseHTMLString: true,message: msg});  } else {this.isdeling = false;this.fileList = [];this.fileData = '';this.cancelDialog();this.$message.error(res.msg);}} else {this.isdeling = false;this.fileList = [];this.fileData = '';this.cancelDialog();this.$message.error(this.message);return;}},async upload() {let name = '其他入库单导入模板.xls'const res = await this.$api.invOrder.otherInstockOrder.getTpl({asId:userInfo.value.currentAsId})let data = res.data;let url = window.URL.createObjectURL(new Blob([data], { type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" }))let link = document.createElement('a')link.style.display = 'none'link.href = url;link.setAttribute('download', name)document.body.appendChild(link)link.click()document.body.removeChild(link)},}};</script><style lang="less" scoped>.firstStep {margin: 24px 0 12px;}.upload {cursor: pointer;color: #4f71ff;}</style>

后台代码也是固定的写法

    @GetMapping("getTpl")@ApiOperation("其他入库单导入模板")public void getSalOutstockTpl(@ApiParam(value = "账套ID", required = true) @RequestParam Integer asId,HttpServletResponse response){String fileName = "其他入库单导入模板.xls";//设置响应头try (InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream("xls/" + fileName);OutputStream os = response.getOutputStream();) {response.setContentType("application/octet-stream");response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));byte[] buf = new byte[1024];int len = 0;while ((len = is.read(buf)) != -1) {os.write(buf, 0, len);}} catch (IOException e) {e.printStackTrace();}}

2 导入解析
excel的模板样式如下图
1
controller层,基本是套路,因为具体实现逻辑是在service层

 @ApiOperation("导入")@PostMapping("imp")public ResponseResult<ExcelErrMsg> imp(Integer asId, Integer userId, MultipartFile file){ResponseResult<ExcelErrMsg> resp = new ResponseResult<>(true, "导入完成!");try {ExcelErrMsg excelErrMsg = otherInstockListExcelService.imp(asId, userId, file);resp.setData(excelErrMsg);} catch (ExcelCommonException | ExcelAnalysisException e) {log.error(e.getMessage(), e);resp = new ResponseResult<>(false, "请使用正确的导入模板导入");} catch (RuntimeException e) {log.error(e.getMessage(), e);resp = new ResponseResult<>(false, e.getMessage());} catch (Exception e) {log.error(e.getMessage(), e);resp = new ResponseResult<>(false, "导入失败!");}return resp;}

service层,这里相当于是基于easyexcel定义的一个算法模板,基本解析套路也就这样。

 @Overridepublic ExcelErrMsg imp(Integer asId, Integer createUser, MultipartFile file) throws IOException {List<OtherInstockExcelDto> lines =new ArrayList<>();List<ExcelErrDetail> excelErrDetails = new ArrayList<>();// 开始处理excelEasyExcel.read(file.getInputStream(), new AnalysisEventListener<Map<Integer, String>>() {/*** 从这里判断出模板类型,目前是根据列数*/@Overridepublic void invokeHead(Map<Integer, CellData> headMap, AnalysisContext context) {//校验表头if (headMap.isEmpty()) {throw new RuntimeException("导入模板表头不可为空");}super.invokeHead(headMap, context);}/*** 表头信息*   检查的信息有限*   1. 检查表头是否存在*   2. 检查列数是否正确* @param headMap* @param context*/@Overridepublic void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {}/*** 解析行信息* @param integerStringMap* @param analysisContext*/@Overridepublic void invoke(Map<Integer, String> integerStringMap, AnalysisContext analysisContext) {//数据行号Integer rowIndex = analysisContext.readRowHolder().getRowIndex() + 1;// 行级校验if (rowIndex>4){// 数据是从第5行开始checkRow(integerStringMap,rowIndex,lines,excelErrDetails);}}/*** excel解析完毕后的数据处理*   逻辑是先完善基础设置,然后再写入期初*   处理辅助核算、处理科目、处理期初这三个顺序不能变* @param analysisContext*/@Overridepublic void doAfterAllAnalysed(AnalysisContext analysisContext) {// 校验数据的正确性checkRecords(asId,lines,excelErrDetails);// 过滤掉存在问题的科目数据List<OtherInstockExcelDto> noerrorList = lines.stream().filter(a -> !a.isHasError()).collect(Collectors.toList());// 处理没有问题的信息handle(asId,createUser,noerrorList);}/*** 处理异常* @param exception* @param context* @throws Exception*/@Overridepublic void onException(Exception exception, AnalysisContext context) throws Exception {if (exception instanceof RuntimeException) {throw (RuntimeException) exception;}}}).sheet().doRead();// 处理excel中的错误信息List<ExcelErrDetail> sortErrs = excelErrDetails.stream().sorted(Comparator.comparing(ExcelErrDetail::getIndex)).collect(Collectors.toList());List<Integer> errs = sortErrs.stream().map(ExcelErrDetail::getIndex).distinct().collect(Collectors.toList());ExcelErrMsg excelErrMsg = new ExcelErrMsg();// 所有条数求和excelErrMsg.setTotalNum(lines.size());excelErrMsg.setFailNum(errs.size());excelErrMsg.setSuccessNum(excelErrMsg.getTotalNum() - excelErrMsg.getFailNum());excelErrMsg.setFailReason(sortErrs);return excelErrMsg;}

校验excel中基本行数据,这里就不展开,因为这里是并不与数据库有交互,因为一次性校验,查看那些数据有问题,所以遇到错误不需要停止。

private void checkRow(Map<Integer, String> row, Integer rowIndex, List<OtherInstockExcelDto> records, List<ExcelErrDetail> excelErrDetails,PsiAccountSet psiAccountSet){OtherInstockExcelDto line = new OtherInstockExcelDto();line.setIndex(rowIndex);// *单据日期if (CheckEmptyUtil.isEmpty(row.get(0))){excelErrDetails.add(new ExcelErrDetail(rowIndex, "单据日期不能为空"));line.setHasError(true);}line.setBillDate(DateUtil.parseDate(row.get(0)));// *数量if (CheckEmptyUtil.isEmpty(row.get(13))){excelErrDetails.add(new ExcelErrDetail(rowIndex, "数量不能为空"));line.setHasError(true);}line.setSl(ExcelUtil.getNumber(row.get(13),psiAccountSet.getSlxsw()));//records.add(line);}

接着有些数据导入之前是一些基础数据,因此需要判断数据库中是否存在,因此进入到第二层筛选
为什么会有下面的代码,因为财务辅助核算中供应商、客户、存货基础数据都是在一张表中,有的用户,客户资料非常之多,因此即使你使用二级缓存,将数据全部加载进去,也是非常消耗性能的,因此获取基础数据的逻辑就是用到了什么,就取什么。

  /*** 获取辅助核算的map,key:code_type,value:id* 包含:供应商、客户、存货* @param records* @return*/Map<String,Integer> getCodeIdMap(Integer asId,List<OtherInstockExcelDto> records){if (!CheckEmptyUtil.isEmpty(records)){Set<String> codes = new HashSet<>();for (OtherInstockExcelDto record: records){if (!CheckEmptyUtil.isEmpty(record.getVendorCode())){codes.add(record.getVendorCode());}if (!CheckEmptyUtil.isEmpty(record.getCustomerCode())){codes.add(record.getCustomerCode());}if (!CheckEmptyUtil.isEmpty(record.getStockCode())){codes.add(record.getStockCode());}}List<Integer> aaTypes = Arrays.asList(CommonSettingConstants.AssistingAccountingType.STOCK_ID,CommonSettingConstants.AssistingAccountingType.CUSTOMER_ID,CommonSettingConstants.AssistingAccountingType.VENDOR_ID);return accAssistingAccountingService.getCodeIdMap(asId,aaTypes,new ArrayList<>(codes));}return new HashMap<>();}

校验基础资料,取出最小集出来,然后判断系统中是否存在。如果不存在,则需要在导入之前先维护。

 /*** 基础数据的正确性* @param records*/public void checkRecords(Integer asId,List<OtherInstockExcelDto> records,List<ExcelErrDetail> excelErrDetails,PsiAccountSet psiAccountSet){// 辅助核算:商品信息、供应商、客户辅助核算Map<String,Integer> aaMap = getCodeIdMap(asId,records);// 仓库信息Map<String,String> whMap = psiInvWarehouseService.getNameIdMap(asId);// 数据字典:单位、入库类型Map<String,String> ccMap = ccsDataDictionaryService.selectNameIdMap(asId,Arrays.asList(CommonSettingConstants.CcsDataDictType.UNIT,CommonSettingConstants.CcsDataDictType.INSTOCK));//for (OtherInstockExcelDto record: records){// 单据日期if (DateUtil.getFirstDate(Integer.parseInt(psiAccountSet.getYear()),Integer.parseInt(psiAccountSet.getMonth())).compareTo(record.getBillDate())>0){excelErrDetails.add(new ExcelErrDetail(record.getIndex(), String.format("单据日期:%s,不能在账套的启用日期之前,请先调整", record.getBillDate())));record.setHasError(true);}// 供应商if (!CheckEmptyUtil.isEmpty(record.getVendorCode())){if (!aaMap.containsKey(record.getVendorCode()+ BaseConstant.Separate.UNDERLINE+CommonSettingConstants.AssistingAccountingType.VENDOR_ID)){excelErrDetails.add(new ExcelErrDetail(record.getIndex(), String.format("供应商编码:%s,在岁月进销存中不存在,请先添加", record.getVendorCode())));record.setHasError(true);} else{record.setVendorId(aaMap.get(record.getVendorCode()+ BaseConstant.Separate.UNDERLINE+CommonSettingConstants.AssistingAccountingType.STOCK_ID));}}}

最后的handle是将正常的数据写入到系统中,这里就不再追溯,根据各自业务系统的逻辑来。

3 导入异常处理
异常处理的包含两方面,一方面在页面上给出提示,另一方面将错误信息导出到excel中。这样有助于排查错误在什么地方。
1
这里就用到了,将json数据转成excel下载。操作在页面中实现

tool.json2Excel = (dataSource,title)=> {var wopts = {bookType: 'xls',bookSST: false,type: 'binary'};var workBook = {SheetNames: ['Sheet1'],Sheets: {},Props: {}};//1、XLSX.utils.json_to_sheet(data) 接收一个对象数组并返回一个基于对象关键字自动生成的“标题”的工作表,默认的列顺序由使用Object.keys的字段的第一次出现确定//2、将数据放入对象workBook的Sheets中等待输出workBook.Sheets['Sheet1'] = XLSX.utils.json_to_sheet(dataSource)//3、XLSX.write() 开始编写Excel表格//4、changeData() 将数据处理成需要输出的格式saveAs(new Blob([changeData(XLSX.write(workBook, wopts))], {type: 'application/octet-stream'}),title)
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/722467.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Spring中@import注解终极揭秘!

技术概念 它能干啥 Import注解在Spring框架中主要用于解决模块化和配置管理方面的技术问题&#xff0c;它可以帮助开发者实现以下几个目标&#xff1a; 模块化配置&#xff1a;在大型项目中&#xff0c;通常需要将配置信息分散到多个配置类中&#xff0c;以便更好地组织和管…

FPGA-DDS原理及实现

DDS(Direct Digital Synthesizer)即数字合成器,是一种新型的频率合成技术,具有相对带宽大,频率转换时间短、分辨率高和相位连续性好等优点。较容易实现频率、相位以及幅度的数控调制,广泛应用于通信领域。 相位累加器是由N位加法器与N位寄存器构成,每个时钟周期的上升沿,加法器…

数据中心制冷系统设计与发展

数据中心概要与传统建筑空间相比&#xff0c;数据中心散热密度大&#xff0c;单位面积散热量可达传统办公区域的40倍以上&#xff0c;且越来越呈现集中化、大型化的趋势&#xff1b;同时&#xff0c;设备的安全性需求提高了对内部空调温湿度和洁净度的要求&#xff0c;数据中心…

【Qt】Qwidget的常见属性

目录 一、Qwidget核心属性 二、enable属性 三、geometry属性 四、 WindowFrame的影响 五、windowTitle属性 六、windowIcon属性 七、qrc文件管理资源 八、windowOpacity属性 九、cursor属性 十、font属性 十一、toolTip属性 十二、focusPolicy属性 十三、styleShe…

STM32FreeRTOS-事件组1(STM32Cube高效开发教程)

文章目录 一、事件组的原理和功能1、事件组与队列信号量特点2、事件组存储结构3、事件组运行原理 二、事件组部分函数1、xEventGroupCreate()创建事件组函数2、xEventGroupSetBits&#xff08;&#xff09;事件组置位函数3、xEventGroupSetBitsFromISR&#xff08;&#xff09;…

Sychronized和ReentrantLock锁 面试题

Sychronized和ReentrantLock锁 面试题 前言1、Java死锁如何避免&#xff1f;2、公平锁和⾮公平锁的底层实现&#xff1f;3、ReentrantLock中tryLock()和lock()⽅法的区别&#xff1f;4、Sychronized的偏向锁、轻量级锁、重量级锁&#xff1f;5、谈谈你对AQS的理解&#xff0c;A…

SQL技巧笔记(一):连续3人的连号问题—— LeetCode601.体育馆的人流量

SQL 技巧笔记 前言&#xff1a;我发现大数据招聘岗位上的应聘流程都是需要先进行笔试&#xff0c;其中占比很大的部分是SQL题目&#xff0c;经过一段时间的学习之后&#xff0c;今天开了一个力扣年会员&#xff0c;我觉得我很有必要去多练习笔试题目&#xff0c;这些题目是有技…

代码随想录算法训练营第三十七天 | LeeCode 738. 单调递增的数字

题目链接&#xff1a;738. 单调递增的数字 - 力扣&#xff08;LeetCode&#xff09; class Solution { public:int monotoneIncreasingDigits(int N) {string strNum to_string(N);// flag用来标记赋值9从哪里开始// 设置为这个默认值&#xff0c;为了防止第二个for循环在fla…

Linux - 进程概念

1、冯诺依曼体系结构 我们常见的计算机&#xff0c;如笔记本。我们不常见的计算机&#xff0c;如服务器&#xff0c;大部分都遵守冯诺依曼体系&#xff1b; 截至目前&#xff0c;我们所认识的计算机&#xff0c;都是有一个个的硬件组件组成&#xff1a; 输入单元&#xff1a;…

【JavaEE】_Spring MVC项目使用数组与集合传参

目录 1. 使用数组传参 1.2 传递单个参数 1.3 传递多个名称相同的参数 1.3.1 关于urlencode 2. 使用集合传参 1. 使用数组传参 创建一个Spring MVC项目&#xff0c;其中 .java文件内容如下&#xff1a; package com.example.demo.controller;import com.example.demo.Per…

2.Zookeeper集成springboot操作节点,事件监听,分布式锁实现

1.Springboot项目中添加zookeeper 已经对应的客户端依赖 &#xff0c;pom.xml文件如下 <!-- Zookeeper组件 --><dependency><groupId>org.apache.zookeeper</groupId><artifactId>zookeeper</artifactId><version>3.9.1</version…

【C++】6-8 评委打分 分数 10

6-8 评委打分 分数 10 全屏浏览 切换布局 作者 刘利 单位 惠州学院 某诗歌朗诵比赛&#xff0c;有n位评委给参赛者打分&#xff0c;计算总分时要去除最高分和对低分。 要求&#xff1a;编写名为cmax和cmin的函数分别返回最高分的和最低分元素的引用&#xff0c;带有2个形参…

leetcode面试经典算法题——1

链接&#xff1a;https://leetcode.cn/studyplan/top-interview-150/ 392. 判断子序列 给定字符串 s 和 t &#xff0c;判断 s 是否为 t 的子序列。 字符串的一个子序列是原始字符串删除一些&#xff08;也可以不删除&#xff09;字符而不改变剩余字符相对位置形成的新字符串…

292.【华为OD机试】跳马问题(广度优先搜索(BFS)JavaPythonC++JS实现)

🚀点击这里可直接跳转到本专栏,可查阅顶置最新的华为OD机试宝典~ 本专栏所有题目均包含优质解题思路,高质量解题代码(Java&Python&C++&JS分别实现),详细代码讲解,助你深入学习,深度掌握! 文章目录 一. 题目二.解题思路三.题解代码Python题解代码JAVA题解…

分布式事务(SeataServer)

SeataServer搭建 Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。AT模式是阿里首推的模式,阿里云上有商用版本的GTS(Global Transaction Servi…

JavaScript 闭包 作用域

闭包 JavaScript 中的闭包是相当重要的概念并且与作用域相关知识的指向密切相关 JavaScript 中的作用域是什么意思?闭包会在哪些场景中使用?通过定时器循环输出自增的数字通过 JS 的代码如何实现? 闭包概念一 闭包是指有权访问另外一个函数作用域中的变量的函数。 闭包…

自我对比: 通过不一致的解决视角更好地进行反思

一、写作动机&#xff1a; LLM 在自我评价时往往过于自信或随意性较大&#xff0c;提供的反馈固执或不一致&#xff0c;从而导致反思效果不佳。为了解决这个问题&#xff0c;作者提倡 "自我对比"&#xff1a; 它可以根据要求探索不同的解决角度&#xff0c;对比差异…

ChatGPT如何辅助医生改善AD患者教育的效果

特应性皮炎&#xff08;AD&#xff09;是一种常见的慢性炎症性皮肤病&#xff0c;在全球范围内造成了巨大的疾病负担。尽管在治疗方面取得了一定进展&#xff0c;但AD患者的生活质量较低&#xff0c;治疗满意度差&#xff0c;超过一半的患者认为中度至重度AD疾病控制不佳。AD的…

YOLO快速入门

Yolo简介 概述 YOLO&#xff08;You Only Look Once&#xff09;是一种流行的目标检测算法&#xff0c;由Joseph Redmon等人开发。 YOLO算法以其高效的实时性能和准确的检测能力而闻名。自YOLO的首次提出以来&#xff0c;已经经 历了多个版本的更新和改进。以下是YOLO发展史的…

周边类-找厕所小程序源码

源码获取方式 1&#xff0c;搜一搜 万能工具箱合集 点击资料库 即可进去获取 找厕所小程序源码依赖于腾讯地图的一款源码&#xff0c;腾讯地图api免费申请&#xff0c;是一款免费又永久的不需要服务器的小程序&#xff0c;起个好名字蹭蹭蹭~ 搭建教程&#xff1a; 1、下载源码…