前端实现文件预览img、docx、xlsx、ppt、pdf、md、txt、audio、video

大厂技术  高级前端  Node进阶
点击上方 程序员成长指北,关注公众号
回复1,加入高级Node交流群

前言

最近有接到一个需求,要求前端支持上传制定后缀文件,且支持页面预览,上传简单,那么预览该怎么实现呢,尤其是不同类型的文件预览方案,那么下面就我这个需求的实现,答案在下面 👇:

「具体的预览需求:」预览需要支持的文件类型有:png、jpg、jpeg、docx、xlsx、ppt、pdf、md、txt、audio、video,另外对于不同文档还需要有定位的功能。例如:pdf 定位到页码,txtmarkdown定位到文字并滚动到指定的位置,音视频定位到具体的时间等等。


「⚠️ 补充:我的需求是需要先将文件上传到后台,所以我拿到的是url地址去展示,对于markdowntxt的文件需要先用fetch获取,其他的展示则直接使用url链接就可以。」

不同文件的实现方式不同,下面分类讲解,总共分为以下几类:

  1. 自有标签文件:png、jpg、jpeg、audio、video

  2. 纯文字的文件:markdown & txt

  3. office 类型的文件:docx、xlsx、ppt

  4. embed 引入文件:pdf

  5. iframe:引入外部完整的网站


自有标签文件:png、jpg、jpeg、audio、video

对于图片、音视频的预览,直接使用对应的标签即可,如下:

图片:png、jpg、jpeg

「示例代码:」

<img src={url} key={docId} alt={name} width="100%" />;

「预览效果如下:」

35bda558f4c59edb1ba43a6e3132a098.png

音频:audio

「示例代码:」

<audio ref={audioRef} controls controlsList="nodownload" style={{ width: '100%' }}>
  <track kind="captions" />
  <source src={url} type="audio/mpeg" />
</audio>

「预览效果如下:」

a63282de8cd5ddb741e68537aa560a60.png

视频:video

「示例代码:」

<video ref={videoRef} controls muted controlsList="nodownload" style={{ width: '100%' }}>
  <track kind="captions" />
  <source src={url} type="video/mp4" />
</video>

「预览效果如下:」

4d082edf982c7bf1e9985610d091711b.png

「关于音视频的定位的完整代码:」

import React, { useRef, useEffect } from 'react';

interface IProps {
  type: 'audio' | 'video';
  url: string;
  timeInSeconds: number;
}

function AudioAndVideo(props: IProps) {
  const { type, url, timeInSeconds } = props;
  const videoRef = useRef<HTMLVideoElement>(null);
  const audioRef = useRef<HTMLAudioElement>(null);

  useEffect(() => {
    // 音视频定位
    const secondsTime = timeInSeconds / 1000;
    if (type === 'audio' && audioRef.current) {
      audioRef.current.currentTime = secondsTime;
    }
    if (type === 'video' && videoRef.current) {
      videoRef.current.currentTime = secondsTime;
    }
  }, [type, timeInSeconds]);

  return (
    <div>
      {type === 'audio' ? (
        <audio ref={audioRef} controls controlsList="nodownload" style={{ width: '100%' }}>
          <track kind="captions" />
          <source src={url} type="audio/mpeg" />
        </audio>
      ) : (
        <video ref={videoRef} controls muted controlsList="nodownload" style={{ width: '100%' }}>
          <track kind="captions" />
          <source src={url} type="video/mp4" />
        </video>
      )}
    </div>
  );
}

export default AudioAndVideo;

纯文字的文件:markdown & txt

对于markdown、txt类型的文件,如果拿到的是文件的url的话,则无法直接显示,需要请求到内容,再进行展示。

markdown 文件

在展示markdown文件时,需要满足字体高亮、代码高亮、如果有字体高亮,需要滚动到字体所在位置、如果有外部链接,需要新开tab页面再打开。

需要引入两个库:

marked:它的作用是将markdown文本转换(解析)为HTML

highlight:它允许开发者在网页上高亮显示代码。

「字体高亮的代码实现:」

高亮的样式,可以在行间样式定义

const highlightAndMarkFirst = (text: string, highlightText: string) => {
    let firstMatchDone = false;
    const regex = new RegExp(`(${highlightText})`, 'gi');
    return text.replace(regex, (match) => {
      if (!firstMatchDone) {
        firstMatchDone = true;
        return `<span id='first-match' style="color: red;">${match}</span>`;
      }
      return `<span style="color: red;">${match}</span>`;
    });
  };

「代码高亮的代码实现:」

需要借助hljs这个库进行转换

marked.use({
    renderer: {
      code(code, infostring) {
        const validLang = !!(infostring && hljs.getLanguage(infostring));
        const highlighted = validLang
          ? hljs.highlight(code, { language: infostring, ignoreIllegals: true }).value
          : code;
        return `<pre><code class="hljs ${infostring}">${highlighted}</code></pre>`;
      } 
    },
  });

「链接跳转新tab页的代码实现:」

marked.use({
   renderer: {
      // 链接跳转
      link(href, title, text) {
        const isExternal = !href.startsWith('/') && !href.startsWith('#');
        if (isExternal) {
          return `<a href="${href}" title="${title}" target="_blank" rel="noopener noreferrer">${text}</a>`;
        }
        return `<a href="${href}" title="${title}">${text}</a>`;
      },
    },
});

「滚动到高亮的位置的代码实现:」

需要配合上面的代码高亮的方法

const firstMatchElement = document.getElementById('first-match');
if (firstMatchElement) {
    firstMatchElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
}

「完整的代码如下:」

入参的docUrlmarkdown文件的线上url地址,searchText 是需要高亮的内容。

import React, { useEffect, useState, useRef } from 'react';
import { marked } from 'marked';
import hljs from 'highlight.js';

const preStyle = {
  width: '100%',
  maxHeight: '64vh',
  minHeight: '64vh',
  overflow: 'auto',
};

// Markdown展示组件
function MarkdownViewer({ docUrl, searchText }: { docUrl: string; searchText: string }) {
  const [markdown, setMarkdown] = useState('');
  const markdownRef = useRef<HTMLDivElement | null>(null);

  const highlightAndMarkFirst = (text: string, highlightText: string) => {
    let firstMatchDone = false;
    const regex = new RegExp(`(${highlightText})`, 'gi');
    return text.replace(regex, (match) => {
      if (!firstMatchDone) {
        firstMatchDone = true;
        return `<span id='first-match' style="color: red;">${match}</span>`;
      }
      return `<span style="color: red;">${match}</span>`;
    });
  };

  useEffect(() => {
    // 如果没有搜索内容,直接加载原始Markdown文本
    fetch(docUrl)
      .then((response) => response.text())
      .then((text) => {
        const highlightedText = searchText ? highlightAndMarkFirst(text, searchText) : text;
        setMarkdown(highlightedText);
      })
      .catch((error) => console.error('加载Markdown文件失败:', error));
  }, [searchText, docUrl]);

  useEffect(() => {
    if (markdownRef.current) {
      // 支持代码高亮
      marked.use({
        renderer: {
          code(code, infostring) {
            const validLang = !!(infostring && hljs.getLanguage(infostring));
            const highlighted = validLang
              ? hljs.highlight(code, { language: infostring, ignoreIllegals: true }).value
              : code;
            return `<pre><code class="hljs ${infostring}">${highlighted}</code></pre>`;
          },
          // 链接跳转
          link(href, title, text) {
            const isExternal = !href.startsWith('/') && !href.startsWith('#');
            if (isExternal) {
              return `<a href="${href}" title="${title}" target="_blank" rel="noopener noreferrer">${text}</a>`;
            }
            return `<a href="${href}" title="${title}">${text}</a>`;
          },
        },
      });
      const htmlContent = marked.parse(markdown);
      markdownRef.current!.innerHTML = htmlContent as string;
      // 当markdown更新后,检查是否需要滚动到高亮位置
      const firstMatchElement = document.getElementById('first-match');
      if (firstMatchElement) {
        firstMatchElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
      }
    }
  }, [markdown]);

  return (
    <div style={preStyle}>
      <div ref={markdownRef} />
    </div>
  );
}

export default MarkdownViewer;

「预览效果如下:」

7c65dcf913751581ab1e8d0091eb827c.png

txt 文件预览展示

支持高亮和滚动到指定位置

「支持高亮的代码:」

function highlightText(text: string) {
    if (!searchText.trim()) return text;
    const regex = new RegExp(`(${searchText})`, 'gi');
    return text.replace(regex, `<span style="color: red">$1</span>`);
  }

「完整代码:」

import React, { useEffect, useState, useRef } from 'react';
import { preStyle } from './config';

function TextFileViewer({ docurl, searchText }: { docurl: string; searchText: string }) {
  const [paragraphs, setParagraphs] = useState<string[]>([]);
  const targetRef = useRef<HTMLDivElement | null>(null);

  function highlightText(text: string) {
    if (!searchText.trim()) return text;
    const regex = new RegExp(`(${searchText})`, 'gi');
    return text.replace(regex, `<span style="color: red">$1</span>`);
  }

  useEffect(() => {
    fetch(docurl)
      .then((response) => response.text())
      .then((text) => {
        const highlightedText = highlightText(text);
        const paras = highlightedText
          .split('\n')
          .map((para) => para.trim())
          .filter((para) => para);
        setParagraphs(paras);
      })
      .catch((error) => {
        console.error('加载文本文件出错:', error);
      });
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [docurl, searchText]);

  useEffect(() => {
    //  处理高亮段落的滚动逻辑
    const timer = setTimeout(() => {
      if (targetRef.current) {
        targetRef.current.scrollIntoView({ behavior: 'smooth', block: 'center' });
      }
    }, 100);

    return () => clearTimeout(timer);
  }, [paragraphs]);

  return (
    <div style={preStyle}>
      {paragraphs.map((para: string, index: number) => {
        const paraKey = para + index;

        // 确定这个段落是否包含高亮文本
        const isTarget = para.includes(`>${searchText}<`);
        return (
          <p key={paraKey} ref={isTarget && !targetRef.current ? targetRef : null}>
            <div dangerouslySetInnerHTML={{ __html: para }} />
          </p>
        );
      })}
    </div>
  );
}

export default TextFileViewer;

「预览效果如下:」

63bcf9b6fff5cc5664ac9592ba617da5.png


office 类型的文件:docx、xlsx、ppt

docx、xlsx、ppt 文件的预览,用的是office的线上预览链接 + 我们文件的线上url即可。

关于定位:用这种方法我暂时尝试是无法定位页码的,所以定位的功能我采取的是后端将office 文件转成pdf,再进行定位,如果只是纯展示,忽略这个问题即可。

「示例代码:」

<iframe
    src={`https://view.officeapps.live.com/op/view.aspx?src=${url}`}
    width="100%"
    height="500px"
    frameBorder="0"
></iframe>

「预览效果如下:」

018c38692878c5f866ae7e9537813988.png


embed 引入文件:pdf

pdf文档预览时,可以采用embed的方式,这个httpsUrl就是你的pdf文档的链接地址

「示例代码:」

<embed src={`${httpsUrl}`} style={preStyle} key={`${httpsUrl}`} />;

关于定位,其实是地址上拼接的页码sourcePage,如下:

const httpsUrl = sourcePage
        ? `${doc.url}#page=${sourcePage}`
        : doc.url;
        
<embed src={`${httpsUrl}`} style={preStyle} key={`${httpsUrl}`} />;

「预览效果如下:」

4a8aa0ed7c1c60eae1551cecf4bca5cb.png


iframe:引入外部完整的网站

除了上面的各种文件,我们还需要预览一些外部的网址,那就要用到iframe的方式

「示例代码:」

<iframe
    title="网址"
    width="100%"
    height="100%"
    src={doc.url}      
    allow="microphone;camera;midi;encrypted-media;"/>

「预览效果如下:」

115ba316b437c1310a97f29f66699ac1.png


总结:到这里我们支持的所有文件都讲述完了,有什么问题,欢迎评论区留言!

Node 社群

 
 

我组建了一个氛围特别好的 Node.js 社群,里面有很多 Node.js小伙伴,如果你对Node.js学习感兴趣的话(后续有计划也可以),我们可以一起进行Node.js相关的交流、学习、共建。下方加 考拉 好友回复「Node」即可。

82e6c20c89eddc32e68d8a3429c48306.png

“分享、点赞、在看” 支持一下
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要在Vue应用中实现本地预览Word(.docx)、Excel(.xlsx)和PDF文件,你可以使用一些现有的库和插件。以下是一种常见的方法,使用`vue-pdf`、`xlsx`和`mammoth.js`库来实现此功能: 1. 首先,安装所需的库: ```bash npm install vue-pdf xlsx mammoth ``` 2. 创建一个Vue组件,并导入所需的库: ```vue <template> <div> <div v-if="fileType === 'pdf'"> <pdf :src="fileUrl" /> </div> <div v-else-if="fileType === 'xlsx'"> <div>{{ excelData }}</div> </div> <div v-else-if="fileType === 'docx'"> <div v-html="wordContent"></div> </div> </div> </template> <script> import { read, utils } from 'xlsx'; import mammoth from 'mammoth'; import { pdf } from 'vue-pdf'; export default { components: { pdf, }, data() { return { fileType: '', fileUrl: '', excelData: [], wordContent: '', }; }, mounted() { this.loadFile(); }, methods: { loadFile() { const file = 'path/to/your/file'; const extension = file.split('.').pop(); if (extension === 'pdf') { this.fileType = 'pdf'; this.fileUrl = file; } else if (extension === 'xlsx') { this.fileType = 'xlsx'; this.loadExcelFile(file); } else if (extension === 'docx') { this.fileType = 'docx'; this.loadWordFile(file); } }, loadExcelFile(file) { const reader = new FileReader(); reader.onload = (e) => { const data = new Uint8Array(e.target.result); const workbook = read(data, { type: 'array' }); const worksheet = workbook.Sheets[workbook.SheetNames[0]]; this.excelData = utils.sheet_to_json(worksheet, { header: 1 }); }; reader.readAsArrayBuffer(file); }, loadWordFile(file) { const reader = new FileReader(); reader.onload = (e) => { const arrayBuffer = e.target.result; const options = {}; mammoth.extractRawText({ arrayBuffer }, options) .then((result) => { this.wordContent = result.value; }) .catch((error) => { console.error(error); }); }; reader.readAsArrayBuffer(file); }, }, }; </script> ``` 在上面的代码中,你需要将`path/to/your/file`替换为实际文件的路径。根据文件的扩展名,组件将显示不同的预览方式。 请注意,该示例仅提供了一种基本实现方法。你可以根据自己的需求进行修改和调整。 希望这可以帮助到你!如果你有其他问题,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值