vue3 用xlsx 解决 excel 低版本office无法打开问题

文章讲述了在Vue项目中使用vue3-json-excel遇到兼容性问题后,转而采用xlsx库进行json到Excel的导出,包括设置样式、动态调整列宽,以及提供导出按钮样式代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

🚀 个人简介:某大型测绘遥感企业资深Webgis开发工程师,软件设计师(中级)、优快云优质创作者
💟 作 者:柳晓黑胡椒❣️
📝 专 栏:vue实践
🌈 若有帮助,还请关注点赞收藏,不行的话我再努努力💪💪💪

需求背景

原使用 vue3-json-excel ,导致在笔记本office环境下,出现兼容性问题

 <vue3-json-excel class="export-btn" :fetch="excelGetList" :fields="jsonFields" header="告警配置列表" name="告警配置列表.xls" type="xls">
   <span>批量导出</span>
 </vue3-json-excel>

生成的.xls文件,提示文件格式和扩展名不匹配,文件可能已损坏或不安全在这里插入图片描述
生成的.xlsx文件,提示文件格式或文件扩展名无效
在这里插入图片描述

解决思路

既然 vue3-json-excel 达不到效果,就改用xlsx

解决效果

在这里插入图片描述

将json导出为excel

/**
 * 将json数据导出为excel
 * @param {object} options
 * @param {[]} data 数据源 // {'供热系统': "大唐热电供热系统", '供热面积(万m²)': 34.099, '分布式水泵数量': 0, '当前供热系统': "大唐热电供热系统"}
 * @param {string} fileName 文件名称
 * @param [] keySort  字段排序 // ['供热系统', '供热面积(万m²)', '分布式水泵数量','当前供热系统']
 * @param {string} sheetName sheet名称
 * @param {boolean} titleNum 表头数
 */
import XLSXStyle from "xlsx-style-vite" //"^0.0.2"
import FileSaver from "file-saver";// "^2.0.5"
import * as XLSX from "xlsx";//"^0.17.0",

export const exportToExcel = (data, fileName, keySort, sheetName = "sheet1", titleNum = 1) => {
  // const sheet = XLSX.utils.json_to_sheet(data)
  const temp = data.map(item => {
    const arr = []
    keySort.forEach(k => arr.push(item[k]))
    return arr
  })
  temp.unshift(keySort)
  const sheet = XLSX.utils.aoa_to_sheet(temp)
  const wb = {
    SheetNames: [sheetName],
    Sheets: {
      [sheetName]: sheet
    }
  }
  const range = XLSX.utils.decode_range(wb.Sheets[sheetName]['!ref']);
  //单元格边框样式
  const borderStyle = {
    top: {
      style: "thin",
      color: {rgb: "000000"}
    },
    bottom: {
      style: "thin",
      color: {rgb: "000000"}
    },
    left: {
      style: "thin",
      color: {rgb: "000000"}
    },
    right: {
      style: "thin",
      color: {rgb: "000000"}
    }
  };
  const cWidth = [];
  for (let C = range.s.c; C < range.e.c + 1; ++C) {   //SHEET列
    let len = 100; //默认列宽
    const len_max = 400; //最大列宽
    for (let R = range.s.r; R <= range.e.r; ++R) {  //SHEET行
      const cell = {c: C, r: R};                    //二维 列行确定一个单元格
      const cell_ref = XLSX.utils.encode_cell(cell);  //单元格 A1、A2
      if (wb.Sheets[sheetName][cell_ref]) {
        if (R < titleNum) {
          wb.Sheets[sheetName][cell_ref].s = {  //设置第一行单元格的样式 style
            font: {
              sz: 14, // 标题字号
              color: {rgb: '060B0E'},// 颜色
              bold: true//加粗
            },
            alignment: {
              horizontal: 'center',
              vertical: 'center',
            },
            fill: {
              fgColor: {rgb: 'E4E4E4'}, // 背景
            },
            border: borderStyle,//用上面定义好的边框样式
          };
        } else {
          wb.Sheets[sheetName][cell_ref].s = {
            alignment: {
              horizontal: 'center',
              vertical: 'center',
            },
            border: borderStyle,//用上面定义好的边框样式
          };
        }
        //动态自适应:计算列宽
        const va = JSON.parse(JSON.stringify(wb.Sheets[sheetName][cell_ref].v || ''));
        const card1 = JSON.parse(JSON.stringify(va.toString())).match(/[\u4e00-\u9fa5]/g); //匹配中文
        let card11 = "";
        if (card1) card11 = card1.join("")
        const card2 = JSON.parse(JSON.stringify(va.toString())).replace(/([^\u0000-\u00FF])/g, "");  //剔除中文
        let st = 0;
        if (card11) st += card11.length * 20  //中文字节码长度
        if (card2) st += card2.length * 10  //非中文字节码长度
        if (st > len) len = st;
      }
    }
    cWidth.push({'wpx': len > len_max ? len_max : len});     //列宽
  }
  wb.Sheets[sheetName]['!cols'] = cWidth;
  const wopts = {bookType: 'xlsx', bookSST: false, type: 'binary'};
  const wbout = XLSXStyle.write(wb, wopts); //一定要用XLSXStyle不要用XLSX,XLSX是没有格式的!
  FileSaver(new Blob([s2ab(wbout)], {type: ""}), fileName + '.xlsx');

  function s2ab(s) {
    const buf = new ArrayBuffer(s.length);
    const view = new Uint8Array(buf);
    for (let i = 0; i != s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF;
    return buf;
  }
}

将table导为excel

/**
 * 将table导出为excel
 * @param {object} options
 * @param {[]} table 表格元素
 * @param {string} fileName 文件名称
 * @param {string} sheetName sheet名称
 * @param {boolean} titleNum 表头数
 */
export const exportTableToExcel = (dom, fileName, sheetName = "sheet1", titleNum = 1) => {
  const wb = XLSX.utils.table_to_book(dom, {sheet: sheetName, raw: true});
  const range = XLSX.utils.decode_range(wb.Sheets[sheetName]['!ref']);
  //单元格边框样式
  const borderStyle = {
    top: {
      style: "thin",
      color: {rgb: "000000"}
    },
    bottom: {
      style: "thin",
      color: {rgb: "000000"}
    },
    left: {
      style: "thin",
      color: {rgb: "000000"}
    },
    right: {
      style: "thin",
      color: {rgb: "000000"}
    }
  };
  const cWidth = [];
  for (let C = range.s.c; C < range.e.c + 1; ++C) {   //SHEET列
    let len = 100; //默认列宽
    const len_max = 400; //最大列宽
    for (let R = range.s.r; R <= range.e.r; ++R) {  //SHEET行
      const cell = {c: C, r: R};                    //二维 列行确定一个单元格
      const cell_ref = XLSX.utils.encode_cell(cell);  //单元格 A1、A2
      if (wb.Sheets[sheetName][cell_ref]) {
        // if (R == 0){
        if (R < titleNum) {
          wb.Sheets[sheetName][cell_ref].s = {  //设置第一行单元格的样式 style
            font: {
              sz: 14,
              color: {rgb: '060B0E'},
              bold: true
            },
            alignment: {
              horizontal: 'center',
              vertical: 'center',
            },
            fill: {
              fgColor: {rgb: 'E4E4E4'},
            },
            border: borderStyle,//用上面定义好的边框样式
          };
        } else {
          wb.Sheets[sheetName][cell_ref].s = {
            alignment: {
              horizontal: 'center',
              vertical: 'center',
            },
            border: borderStyle,//用上面定义好的边框样式
          };
        }
        //动态自适应:计算列宽
        const va = JSON.parse(JSON.stringify(wb.Sheets[sheetName][cell_ref].v || ''));
        const card1 = JSON.parse(JSON.stringify(va.toString())).match(/[\u4e00-\u9fa5]/g); //匹配中文
        let card11 = "";
        if (card1) {
          card11 = card1.join("")
        }
        const card2 = JSON.parse(JSON.stringify(va.toString())).replace(/([^\u0000-\u00FF])/g, "");  //剔除中文
        let st = 0;
        if (card11) {
          st += card11.length * 20  //中文字节码长度
        }
        if (card2) {
          st += card2.length * 10  //非中文字节码长度
        }
        if (st > len) {
          len = st;
        }
      }
    }
    if (len > len_max) {//最大宽度
      len = len_max;
    }
    cWidth.push({'wpx': len});     //列宽
  }
  wb.Sheets[sheetName]['!cols'] = cWidth.slice(1, -1);
  // 删除列-----重点-----0就是第一列。。
  // wb.Sheets[sheetName]['!cols'][0] = {hidden: true}
  deleteCol(wb.Sheets[sheetName], 0)
  deleteCol(wb.Sheets[sheetName], cWidth.length - 1)
  // 合并列
  // wb.Sheets[sheetName]["!merges"] = [
  //   {  //合并A1A2单元格
  //     s: {//s为开始
  //       c: 0,//开始列
  //       r: 0//开始取值范围
  //     },
  //     e: {//e结束
  //       c: 1,//结束列
  //       r: 0//结束范围
  //     }
  //   }
  // ]
  const wopts = {bookType: 'xlsx', bookSST: false, type: 'binary'};
  const wbout = XLSXStyle.write(wb, wopts); //一定要用XLSXStyle不要用XLSX,XLSX是没有格式的!
  FileSaver(new Blob([s2ab(wbout)], {type: ""}), fileName + '.xlsx');

  function encodeCell(r, c) {
    return XLSX.utils.encode_cell({r, c});
  }

  // 删除行
  function deleteRow(ws, index) {
    const range = XLSX.utils.decode_range(ws['!ref']);
    for (let row = index; row < range.e.r; row++) {
      for (let col = range.s.c; col <= range.e.c; col++) {
        ws[encodeCell(row, col)] = ws[encodeCell(row + 1, col)];
      }
    }
    range.e.r--;
    ws['!ref'] = XLSX.utils.encode_range(range.s, range.e);
  }

  // 删除列
  function deleteCol(ws, index) {
    const range = XLSX.utils.decode_range(ws['!ref']);
    for (let col = index; col < range.e.c; col++) {
      for (let row = range.s.r; row <= range.e.r; row++) {
        ws[encodeCell(row, col)] = ws[encodeCell(row, col + 1)];
      }
    }
    range.e.c--;
    ws['!ref'] = XLSX.utils.encode_range(range.s, range.e);
  }

  function s2ab(s) {
    const buf = new ArrayBuffer(s.length);
    const view = new Uint8Array(buf);
    for (let i = 0; i != s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF;
    return buf;
  }
}

导出样式

应粉丝要求,附上导出样式

import {h} from "vue";
import {ElMessageBox} from "element-plus";

ElMessageBox({
  title: '导出Excel表格',
  draggable: true,
  showCancelButton: true,
  showConfirmButton: false,
  message: h('div', null, [ // 这里用到了h函数
    h(ElButton, { text: true, type: 'primary', innerHTML: '导出选中数据', onClick: assignExport }),
    h(ElButton, { text: true, type: 'success', innerHTML: '导出所有数据', onClick: allExport })
  ]),
  cancelButtonText: '取消',
})
// 导出
const assignExport = () =>{
  console.log(111)
}
const allExport = () =>{
  console.log(222)
}

在这里插入图片描述

### 使用 `vue-office-excel` 打开 XLSX 文件显示空白的解决方案 #### 可能的原因分析 当遇到使用 `@vue-office/excel` 插件打开 `.xlsx` 文件却只显示空白页面的情况时,可能有几种原因造成此现象。一种可能是文件本身存在问题或编码不符合预期;另一种可能性在于插件配置不当或是浏览器兼容性问题。 #### 解决方法一:验证文件完整性 确保所使用的 `.xlsx` 文件未损坏且能够通过其他软件正常读取。可以尝试用 Microsoft Excel 或者 LibreOffice Calc 来确认文件内容是否可正确呈现[^1]。 #### 解决方法二:更新依赖库版本 检查当前项目的 package.json 中关于 `@vue-office/excel` 的版本号,并考虑升级到最新稳定版以获得更好的性能和支持更多的特性。有时旧版本可能存在某些 bug 导致特定情况下无法渲染数据表单[^2]。 #### 解决方法三:调整组件初始化参数设置 对于 Vue 组件而言,在实例化过程中传递给 `<excel-viewer>` 标签内的 props 属性非常重要。如果这些属性值设定不合理,则可能导致加载失败等问题发生。以下是推荐的一个较为通用的例子: ```html <template> <div id="app"> <!-- 设置 key 值来触发重新渲染 --> <excel-viewer :key="fileUrl" v-bind="$attrs" @loaded="onLoaded"/> </div> </template> <script setup lang="ts"> import { ref } from &#39;vue&#39;; const fileUrl = ref(&#39;path/to/your.xlsx&#39;); function onLoaded() { console.log("Excel loaded successfully"); } </script> ``` 上述代码片段展示了如何利用 `v-bind` 将父级传来的所有属性绑定至子组件上,同时监听 `loaded` 事件以便于调试目的打印日志信息。另外值得注意的是这里还加入了动态计算得到的 `key` 参数用于强制刷新视图防止缓存影响[^3]。 #### 解决方法四:引入 polyfill 处理低版本浏览器环境下的兼容性 考虑到部分用户的浏览设备可能运行着较老的操作系统及内核版本,因此建议在项目入口处适当加入一些 Polyfills 库(如 core-js),从而使得更多类型的客户端都能顺利解析并展示由该插件生成的内容。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

柳晓黑胡椒

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值