文件预览
· 阅读需 5 分钟
文件类型:img、docx、xlsx、ppt、pdf、md、txt、audio、video
图片
对于图片、音视频的预览,直接使用对应的标签即可。
<img src="xxx.png" alt="alt" width="100%" />;
音视频
音频
<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>
音视频定位
import { 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 (
<>
{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>
)}
</>
);
}
export default AudioAndVideo;
markdown 文件
markdown、txt 类型的文件,如果拿到的是文件地址的话,则无法直接显示,需要获取到内容,再进行展示。
在展示 markdown 文件时,需要满足:
- 字体高亮、代码高亮
- 如果有字体高亮,需要滚动到字体所在位置
- 如果有外部链接,需要新开 tab 页面再打开
需要引入两个库:
marked
:将markdown
文本转换为HTML
highlight
:在网页上高亮显示代码
import { useEffect, useState, useRef } from 'react';
import { marked } from 'marked';
import hljs from 'highlight.js';
const preStyle = {
width: '100%',
maxHeight: '64vh',
minHeight: '64vh',
overflow: 'auto'
};
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(() => {
fetch(docUrl)
.then(res => res.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;
txt 文件
支持高亮和滚动到指定位置
import { 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);
});
}, [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;
docx、xlsx、ppt
使用 office 的线上预览链接、文件的线上地址
<iframe
src=`https://view.officeapps.live.com/op/view.aspx?src=${url}`
width="100%"
height="500px"
frameBorder="0"
></iframe>
这种方法无法定位页码,可让后端将 office 文件转成 pdf,再进行定位。
pdf
在 pdf 文档预览时,可以采用 embed
的方式,这个 httpsUrl
就是 pdf
文档的链接地址
<embed src="{ pdfUrl }" style="{ preStyle }" key="{ pdfUrl }" />
可在地址上拼接页码 sourcePage
实现定位
const pdfUrl = sourcePage ? `${doc.url}#page=${sourcePage}` : doc.url;
<embed src={pdfUrl} style={preStyle} key={pdfUrl} />;
iframe
引入外部完整的网站
<iframe title="网址" width="100%" height="100%" src="{doc.url}" allow="microphone;camera;midi;encrypted-media;" />