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位寄存器构成,每个时钟周期的上升沿,加法器…

【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;…

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

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

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…

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

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

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

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

使用css的transition属性实现抽屉功能

需求 使用css手写一个抽屉&#xff0c;并且不能遮挡住原来的页面 效果&#xff1a;&#xff08;录的gif有点卡&#xff0c;实际情况很丝滑&#xff09; 实现代码&#xff1a; <template><div class"dashboard-container"><div class"mainBox&…

Java项目:36 springboot图书个性化推荐系统的设计与实现003

作者主页&#xff1a;源码空间codegym 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文中获取源码 项目介绍 springboot003图书个性化推荐系统的设计与实现 管理员&#xff1a;首页、个人中心、学生管理、图书分类管理、图书信息管理、图书预约管理、退…

[element]element-ui框架下载

⭐作者介绍&#xff1a;大二本科网络工程专业在读&#xff0c;持续学习Java&#xff0c;努力输出优质文章 ⭐作者主页&#xff1a;逐梦苍穹 ⭐如果觉得文章写的不错&#xff0c;欢迎点个关注一键三连&#x1f609;有写的不好的地方也欢迎指正&#xff0c;一同进步&#x1f601;…

基于Springboot的足球俱乐部管理系统(有报告)。Javaee项目,springboot项目。

演示视频&#xff1a; 基于Springboot的足球俱乐部管理系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot项目。 项目介绍: 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&#xff…

【Datawhale组队学习:Sora原理与技术实战】Attention和LLM

Attention Attention 注意力&#xff0c;从两个不同的主体开始。 论文&#xff1a;https://arxiv.org/pdf/1703.03906.pdf seq2seq代码仓&#xff1a;https://github.com/google/seq2seq 计算方法&#xff1a; 加性Attention&#xff0c;如&#xff08;Bahdanau attention&…

数据库-ODBC操作

承接Qt/C软件开发项目&#xff0c;高质量交付&#xff0c;灵活沟通&#xff0c;长期维护支持。需求所寻&#xff0c;技术正适&#xff0c;共创完美&#xff0c;欢迎私信联系&#xff01; 一、ODBC 数据源配置 打开ODBC数据源管理器&#xff1a; 在Windows搜索栏中键入“ODBC数…

PyTorch搭建LeNet神经网络

函数的参数 1、PyTorch Tensor的通道排序 [batch, channel, height, width] batch: 要处理的一批图像的个数 channel: 通道数&#xff08;一般是R G B 三个通道&#xff09; height: 图像的高度 width: 图像的宽度 2.Conv 2d 卷积层的参数 [in_channels, out_channels, ke…

Golang 开发实战day01 - Variable String Numeric

Golang 教程01 - Variable String Numeric 1. Go语言的重要性 Go语言&#xff0c;又称Golang&#xff0c;是一种由Google开发的静态编译型编程语言。它于2009年首次发布&#xff0c;并在短短几年内迅速流行起来。Go语言具有以下特点&#xff1a; 语法简单易学&#xff1a;Go…

第一节 JDBC是什么?

JDBC代表Java数据库连接(Java Database Connectivity)&#xff0c;它是用于Java编程语言和数据库之间的数据库无关连接的标准Java API&#xff0c;换句话说&#xff1a;JDBC是用于在Java语言编程中与数据库连接的API。 JDBC库包括通常与数据库使用相关&#xff0c;如下面提到的…

链表哨兵例子

哨兵链表例子_根据值删除链表 package linklist;public class leetcode203 {public static void main(String[] args) {ListNode listNode new ListNode(1,new ListNode(2,new ListNode(3)));ListNode listNode1 removeElements(listNode,2);System.out.println(listNode1);…

LeetCode.232. 用栈实现队列

题目 232. 用栈实现队列 分析 先了解一下栈和队列的特点&#xff1a; 栈&#xff1a;先进后出队列&#xff1a;先进先出 想用栈实现队列的特点&#xff0c;就需要使用两个栈。因为两个栈就可以将列表倒序。 假设第一个栈 s1 [1,2,3]&#xff0c;第二个栈 s2 [] 。若循环…