Node.js之node-xlsx模块

Nodejs包之node-xlsx

支持读写Excel的node.js模块 1. node-xlsx: 基于Node.js解析excel文件数据及生成excel文件,仅支持xlsx格式文件; 2. excel-parser: 基于Node.js解析excel文件数据,支持xls及xlsx格式文件; 3. excel-export : 基于Node.js将数据生成导出excel文件,生成文件格式为xlsx; 4. node-xlrd: 基于node.js从excel文件中提取数据,仅支持xls格式文件。

我们先来个简单小demo吧:

首先要安装这个包依赖: npm install -save nodejs-xlsx

然后就是看下面代码了:

var xlsx = require('node-xlsx');
var fs = require('fs');
//读取文件内容
var obj = xlsx.parse(__dirname+'/test.xlsx');
var excelObj=obj[0].data;
console.log(excelObj);

var data = [];
for(var i in excelObj){
    var arr=[];
    var value=excelObj[i];
    for(var j in value){
        arr.push(value[j]);
    }
    data.push(arr);
}
var buffer = xlsx.build([
    {
        name:'sheet1',
        data:data
    }        
]);

//将文件内容插入新的文件中
fs.writeFileSync('test1.xlsx',buffer,{'flag':'w'});

实际项目中通常用 buffer 流传递数据表进行解析:

实际场景 1 为:客服端上传规定格式的模板表格数据(这里只有一列为【编号】)

exports.uploadExcel = function (args, req, res, next) {
  /**
   * uploadExcel 批量上传商品 Excel
   **/
  const params = {
  
      clusterCode: args.clusterCode.value,
      
      excel: args.excel.value.buffer   // 接受 buffer流表格  
  }
  
  const xlsxHead = ['商品编号'] // 设置规定一个你想要的表头内容
  let excel = null

  try {
  
    excel = xlsx.parse(params.excel) // 解析上传的表格内容 以行为单位
  } catch (e) {
  
    logger.error(e.stack)
    logger.error(e.message)
    
    return res.json({code: -1, message: 'excel解析错误,请确认是正确的excel文件'}).end()
  }

  if (!excel || _.isEmpty(excel[0].data)) {
  
    return res.json({code: -1, message: '上传文件不合法,请重新上传!'}).end()
  }

  const theHead = excel[0].data[0]  // 解析并取出表格的表头
  
  logger.trace('excel表头', theHead)
  
  if (!_.isEqual(theHead, xlsxHead)) { // 此处是为了判断并防止客服端随意乱吃不符合模板要求的表格。
  
    return res.json({ code: -1, message: '该excel不符合格式要求,请下载模板填充数据!' }).end()
  } 

  const rows = excel[0].data.slice(1) // 截取填入的数据部分 按照 行 截取

  if (_.isEmpty(rows)) {
  
    return res.json({code: -1, message: '您还未填完内容呢!'}).end()
  }
  // excel中 商品编号集合  
  let productionNos = rows.map(row =>  _.isEmpty(row) ? '' : row[0])
  
  // 去除空行
  productionNos = _.compact(productionNos)
  
  logger.trace('上传的商品编号集合', productionNos)

  const user = pickUser(req)
  
  const ProductionModel = initProductionModel(user.customerDB)

  
  const fn = co.wrap(function* () {
  
    const products = yield ProductionModel._selectProductsNo(productionNos)
    
    const selectProductionNos = products.map(item => Number(item.productionNo))
    
    const errProductionNos = _.difference(productionNos, selectProductionNos) // 放入不合法的 商品编号
    
    const errArr = errProductionNos.map(num => ({productionNo: num, errMsg: _.isNumber(num) ? '【商品编号】不存在!' : '【商品编号】不是数字!'}))

    if (_.isEmpty(errArr)) {
    
      const obj = {productionNos, clusterCodes:[params.clusterCode]}
      logger.trace('上传商品加入集群参数', obj)
      
      if(_.isEmpty(obj.productionNos)) return res.json({code: -1, message: '您填写的内容有误,请检查!'}).end()
      
      return yield ProductionModel._insertProductionClusterMap(obj)
      
    } else {
    
      return Promise.reject(Utils.cError(-2, 'excel数据存在错误,请检查以下数据!', {products: errArr}))
    }
  })

  fn()
    .then(data => {
    
      res.json({code: 0, message: '添加成功!', data}).end()
      
      addLog(user.customerDB, user.userId, user.ip, 'TAGS', params.clusterCode, '已成功从集群中批量上传商品 Excel!')
      
    })
    .catch(err => {
    
      return Utils.handleErr(res, err)
    })
}

实际场景 2 为:客户端上传表格时,需要下载后台给出的模板表格。

exports.getExcel = function (args, req, res, next) {
  /**
   * getPromotionTaskExcel excel下载
   **/
  const params = {
  
    campaignId: args.promotionCampaignId.value,
    
    fileName: args.fileName.value || '任务表'  // 这里可以自定义下载的文件名称
  }
  logger.trace('下载任务列表参数', params)

  const user = pickUser(req)
  
  const PromotionTaskModel = initializeTaskModel(user.customerDB)
  
  const PromotionMaterialModel = initializeMaterialModel(user.customerDB)
  
  const PromotionAllotMaterialModel = initializeAllotMaterialModel(user.customerDB)

  const fn = co.wrap(function * () { // 将已经加入页面的数据添到要下载的表格中,再继续再表格中添加数据
  
    const tasks = yield PromotionTaskModel._selectTaskList(params)
    
    const materials = yield PromotionMaterialModel._selectMaterials(params)
    
    const allotRecords = yield PromotionAllotMaterialModel.selectAllotRecord(params.campaignId)
    
    // 按任务分组
    const groupAllotRecords = _.groupBy(allotRecords, 'taskId')
    
    // 组合数据
    const thead = ['门店编号', '门店名称', '销售额(元)', '毛利(元)', '新增会员(人)']
    
    materials.forEach(material => {
      thead.push(material.materialName)
    })
    
    const tbody = tasks.map(task => {
    
      const row = [task.organizationId, task.organizationName, task.sales, task.grossProfit, task.newCustomers]
      
      const allotRecord = groupAllotRecords[task.taskId] || []
      
      materials.forEach(material => {
        const allot = _.find(allotRecord, ['materialId', material.materialId])
        
        const count = allot ? allot.count : 0
        
        row.push(count)
      })
      
      return row
    })

    // build excel buffer
    tbody.unshift(thead)
    
    let excelBuffer = Buffer.from('')
    
    try {
    
      excelBuffer = xlsx.build([{name: '任务表', data: tbody}])
      
      fs.writeFileSync(cwd + `/public/download/temp/${params.fileName}.xlsx`, excelBuffer, {encoding: 'utf8'})
    } catch (e) {
    
      logger.error(e.stack)
      logger.error(e.message)
      
      return Promise.reject(cError(-1, 'Excel 创建失败,请稍候再试!'))
    }
    
    return {downloadLink: `/download/temp/${params.fileName}.xlsx`} // 返回下载相对服务器文件路径
  })

  fn()
    .then(data => {
    
      res.json({code: 0, message: '操作成功!', data}).end()
      
      addLog(user.customerDB, user.userId, user.ip, 'PROMOTION', params.campaignId, '已成功下载营销活动任务列表(excel)')
    })
    
    .catch(e => handleErr(res, e))
}