LOGO 首页 OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 技术文档 其他文档  
 
网站管理员

img,word,excel,pdf文件在线预览

zhenglin
2026年8月3日 16:31 本文热度 152

最近在前端实现了附件预览。原本已经实现了文件的下载功能,但是业务表示需要直接在线预览,后端没有时间,只好由前端实现了。总体方案,使用fetch请求后端服务地址,将响应内容转换为blob流,然后前端使用相关插件,解析blob流内容,将附件内容以html的方式渲染在浏览器中。

1.图片预览

  • 核心代码

   

   //将响应内容转成blob


  response.blob()


 

 //创建对象地址

const imgUrl = URL.createObjectURL(blob);


const img = document.createElement('img');


img.src = imgUrl;


img.onload = () => {


  URL.revokeObjectURL(imgUrl);


};


img.onerror = () => {


  console.error('图片加载失败');


};


document.body.appendChild(img);


2.word预览

利用了docx-preview插件和jszip插件,但是只能实现docx后缀文档的预览,需要后端将doc转成docx 下面是DeepSeek给的方案,最终选择了docx-preview

  • 核心代码

 //将响应内容转成blob


 response.blob()



  

 // 渲染 Word 文档到预览容器


docx.renderAsync(blob, document.getElementById('word-preview'))


    .then(() => {


        console.log('Word document rendered successfully!');


  });

  • 后端解决示例

blog.csdn.net/weixin_4555…

blog.csdn.net/m0_74825678…

3.excel预览

excel预览相对比较复杂了,借助xlsx进行表格数据的解析和handsontable进行表格样式的渲染。涉及到数据量大,多sheet的情况,比如几千条数据,在前端解析并渲染,响应非常慢,很卡顿,于是做了分页,每页100条.

  • 核心代码

 // 第一步:使用 fetch 获取文件流,并使用XLSX解析文件流数据

    async function previewExcel() {

      try {

        const response = await fetch(excelUrl, {

          responseType: 'arraybuffer', // 关键:指定响应类型

        });

        const arrayBuffer = await response.arrayBuffer();

        

        // 解析Excel

        const workbook = XLSX.read(arrayBuffer, { type: 'array',cellStyles: true });

         // 遍历所有 Sheet 页

        workbook.SheetNames.forEach((sheetName) => {

          const worksheet = workbook.Sheets[sheetName];

          const jsonData = XLSX.utils.sheet_to_json(worksheet, { header: 'A', defval: '' ,blankrows:true,cellStyles: true  });

          const mergeCells = getMergeCells(worksheet); // 获取合并单元格信息

          const cellStyles = getCellStyles(worksheet); // 获取单元格样式信息

          sheetsData[sheetName] = {

            data: jsonData,

            mergeCells,

            cellStyles,

          };

        });


        // 默认渲染第一个 Sheet 页

        currentSheetName = workbook.SheetNames[0];

        const { data, mergeCells, cellStyles } = sheetsData[currentSheetName];

        renderTable(data, mergeCells, cellStyles);


        // 渲染 Sheet 页切换菜单

        renderSheetTabs(workbook.SheetNames);


      } catch (error) {

        console.error('预览失败:', error);

      }

    }

    

    //获取合并单元格的数据

    function getMergeCells(worksheet) {

      const merges = worksheet['!merges'] || [];

      return merges.map(merge => ({

        row: merge.s.r,

        col: merge.s.c,

        rowspan: merge.e.r - merge.s.r + 1,

        colspan: merge.e.c - merge.s.c + 1,

      }));

    }


    function convertHexToCssHex(hex) {

        // 检查输入是否为有效的十六进制颜色值(6 位字符)

        if (/^[0-9A-Fa-f]{6}$/.test(hex)) {

            return "#" + hex;

        } else {

            return hex; 

        }

    }

    

    //获取单元格的样式

    function getCellStyles(worksheet) {

      const styles = [];

      //  防止空sheet页整个excel都不渲染

      if (!worksheet) {

        return styles; 

      }

      if (!worksheet['!ref']) {

        return styles;

      }

      const range = XLSX.utils.decode_range(worksheet['!ref']); // 获取工作表范围

      for (let row = range.s.r; row <= range.e.r; row++) {

        for (let col = range.s.c; col <= range.e.c; col++) {

          const cellAddress = XLSX.utils.encode_cell({ r: row, c: col });

          const cell = worksheet[cellAddress];

          if (cell && cell.s) {

            // XLSX 返回的颜色格式为“FFFFFF”,需要在前面加"#"才能正常渲染颜色

            // XLSX未能解析出字体样式

            const fgColor = cell.s.fgColor?.rgb  // "FFFF00"

            const background = convertHexToCssHex(fgColor) ?? 'white'

            styles.push({

              row,

              col,

              style: {

                background,

                color: cell.s.font?.color?.rgb || 'black', // 字体颜色

                fontWeight: cell.s.font?.bold ? 'bold' : 'normal', // 字体加粗

              },

            });

          }

        }

      }

      return styles;

    }


      //  自定义样式渲染器(通用)

      const styleRenderer = (hotInstance, TD, row, col, prop, value, cellProperties) => {

        // 调用默认文本渲染器

        Handsontable.renderers.TextRenderer(hotInstance, TD, row, col, prop, value, cellProperties);

        

        // 获取当前单元格的样式配置

        const { styles } = hotInstance.getSettings();

        const cell = styles.find(s => s.row === row && s.col === col);


        // 应用样式

        if (cell) {

          TD.style.background = cell.style.background || '';

          TD.style.color = cell.style.color || '';

          TD.style.fontWeight = cell.style.fontWeight || '';

        }

      };


      // 注册渲染器

      Handsontable.renderers.registerRenderer('styleRenderer', styleRenderer);


    // 渲染表格到页面

    let hot;

    function renderTable(data,mergeCells, cellStyles) {

        const container = document.getElementById('excel-preview');

        if (!container) {

          console.error('容器元素未找到!');

          return;

        }

        container.innerHTML = ''; // 清空容器

          // 分页数据

        const start = (currentPage - 1) * pageSize;

        const end = start + pageSize;

        const pageData = data.slice(start, end);


          // 生成动态 cell 配置

        const cellConfig = cellStyles.map(style => ({

          row: style.row,

          col: style.col,

          renderer: 'styleRenderer', // 使用注册的自定义渲染器

        }));

        hot = new Handsontable(container, {

          data: pageData, // 表格数据

          rowHeaders: true, // 显示行号

          colHeaders: true, // 显示列号

          mergeCells: mergeCells, // 设置合并单元格

          styles:cellStyles,

          cell: cellConfig, // 动态样式配置

          contextMenu: true, // 启用右键菜单

          manualRowResize: true, // 允许调整行高

          manualColumnResize: true, // 允许调整列宽

          stretchH: 'all', // 拉伸列宽以适应容器

          height: 'auto', // 自动高度

          licenseKey: 'non-commercial-and-evaluation', // 免费版 License

        });


         // 渲染分页导航

        renderPagination(data.length);

    }


    function renderPagination(totalRows) {

      const paginationContainer = document.getElementById('pagination');

      paginationContainer.innerHTML = '';


      const totalPages = Math.ceil(totalRows / pageSize);


      // 上一页按钮

      const prevButton = document.createElement('button');

      prevButton.textContent = '上一页';

      prevButton.disabled = currentPage === 1;

      prevButton.addEventListener('click', () => {

        currentPage--;

        const { data, mergeCells, cellStyles } = sheetsData[currentSheetName];

        renderTable(data, mergeCells, cellStyles);

      });

      paginationContainer.appendChild(prevButton);

      // 页码显示

      const pageInfo = document.createElement('span');

      pageInfo.textContent = `第 ${currentPage} 页 / 共 ${totalPages} 页`;

      paginationContainer.appendChild(pageInfo);


      // 下一页按钮

      const nextButton = document.createElement('button');

      nextButton.textContent = '下一页';

      nextButton.disabled = currentPage === totalPages;

      nextButton.addEventListener('click', () => {

        currentPage++;

        const { data, mergeCells, cellStyles } = sheetsData[currentSheetName];

        renderTable(data, mergeCells, cellStyles);

      });

      paginationContainer.appendChild(nextButton);

    }

   

    function renderSheetTabs(sheetNames) {

      const tabsContainer = document.getElementById('sheet-tabs');

      tabsContainer.innerHTML = ''; // 清空旧内容


      sheetNames.forEach((sheetName) => {

        const tab = document.createElement('button');

        tab.textContent = sheetName;

        tab.className = sheetName === currentSheetName ? 'active' : ''; // 高亮当前 Sheet 页

        tab.addEventListener('click', () => {

          currentSheetName = sheetName;

          currentPage = 1; // 切换 Sheet 页时重置页码

          

          const { data, mergeCells, cellStyles } = sheetsData[currentSheetName];

          renderTable(data, mergeCells, cellStyles);

          updateActiveTab(tab); // 更新高亮状态

        });

        tabsContainer.appendChild(tab);

      });

    }


    function updateActiveTab(activeTab) {

      const tabs = document.querySelectorAll('#sheet-tabs button');

      tabs.forEach((tab) => tab.classList.remove('active'));

      activeTab.classList.add('active');

    }


    document.addEventListener('DOMContentLoaded', function () {

       previewExcel()

    });

以上实现了多sheet,分页,合并单元格,单元格背景色,非标准的表头和表体类表格的渲染,借助了AI和官方文档实现了对应功能,供参考!

  • 参考文档

  1. 15.1.0 handsontable

  2. sheetjs

4.pdf预览

pdf预览是后端实现的,直接打开一个链接就是pdf形式,借助的是pdfjs。chrome浏览器其实也可以直接打开pdf文件,其内置的也是pdfjs


阅读原文:点击这里


该文章在 2026/8/3 16:31:39 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2026 ClickSun All Rights Reserved  粤ICP备13012886号-1  粤公网安备44030602007207号