说明
浏览器方案
利用<iframe>标签预览文件是一种常见的方法,它允许在一个HTML页面中嵌入另一个HTML页面或者指定资源的呈现。这种方式支持多种文件类型,但主要取决于浏览器的能力和配置。以下是一些常见文件类型的预览支持情况及限制:
支持的文件格式:
- Web页面 (
*.html,*.htm): 直接嵌入网页内容。 - 图片 (
*.jpg,*.jpeg,*.png,*.gif,*.svg): 浏览器原生支持显示图片。 - PDF文档 (
*.pdf): 大多数现代浏览器可以直接在<iframe>中显示PDF,无需插件。 - Office文档 (
*.docx,*.xlsx,*.pptx): 需要浏览器支持或Office Online等在线服务来预览。 - 文本文件 (
*.txt,*.csv): 可以直接显示,但可能缺乏格式化。 - 视频与音频 (
*.mp4,*.webm,*.mp3,*.wav): 媒体文件可嵌入并播放,但通常推荐使用<video>或<audio>标签而非<iframe>。
下面介绍一下前端代码实现预览的参考方案
PDF文件预览 (pdfjs-dis)
- 参考:mozilla.github.io/pdf.js/exam…
- demo:
const pdfjsLib = require('pdfjs-dist');
require('pdfjs-dist/build/pdf.worker');
const RenderPdf = (props: { file: any }) => {
const refCanvas = useRef();
const refFileData = useRef();
const [pageTotal, setPageTotal] = useState(0);
const [curPage, setCurPage] = useState(1);
const [url, setUrl] = useState('');
const initFile = async () => {
const fileUrl = await getFileData(props.file?.fileId);
console.log(fileUrl, 'fileUrl');
refFileData.current = fileUrl;
setUrl(fileUrl);
const pdfjsLibModule = await pdfjsLib;
let loadingTask = pdfjsLibModule.getDocument(refFileData.current);
loadingTask.promise.then(function (pdfDoc) {
setPageTotal(pdfDoc.numPages || 0);
});
loadingTask.promise.catch(function (error) {
return Promise.resolve();
});
};
useEffect(() => {
initFile();
}, []);
const renderPagePdf = async (page: number) => {
const pdfjsLibModule = await pdfjsLib;
return new Promise((resolve, reject) => {
try {
let loadingTask = pdfjsLibModule.getDocument(refFileData.current);
loadingTask.promise.then(function (pdfDoc) {
pdfDoc.getPage(page).then((page) => {
// 创建画布
let canvas = refCanvas.current;
let ctx = canvas.getContext('2d');
const viewport = page
.getViewport({
scale: 2,
})
.clone();
canvas.style.height = viewport.height / 3 + 'px';
canvas.style.width = viewport.width / 3 + 'px';
// canvas画大两倍,解决绘画模糊
canvas.height = viewport.height;
canvas.width = viewport.width;
viewport.scale = viewport.scale * 2;
let renderPromise = page.render({
canvasContext: ctx,
viewport: viewport,
}).promise;
renderPromise.then(() => {
resolve({});
});
renderPromise.catch((err) => {
reject(err);
});
});
});
} catch (err) {
reject(err);
}
});
};
const onSizeChange = async (page: number) => {
await renderPagePdf(page);
};
const toNext = () => {
if (curPage < pageTotal) {
setCurPage(curPage + 1);
}
};
const toPre = () => {
if (curPage > 1) {
setCurPage(curPage - 1);
}
};
useEffect(() => {
if (curPage > 0 && refFileData.current) {
setTimeout(() => {
onSizeChange(curPage);
});
}
}, [curPage, pageTotal]);
return (
<div className=''>
<div className='flex justify-center items-center'>
<Button type='link' onClick={toPre}>
上一页
</Button>
{curPage} / {pageTotal}
<Button type='link' onClick={toNext}>
下一页
</Button>
<Button
type='link'
onClick={() => {
window.open(url, '_blank');
}}
>
新窗口全屏查看
</Button>
</div>
<div className='flex justify-center'>
<canvas
style={{
border: '1px solid #eee',
}}
ref={refCanvas}
></canvas>
</div>
</div>
);
};
doc预览 (docx-preview)
import * as doxLib from 'docx-preview';
const RenderDoc = (props: { file: any }) => {
const refDom = useRef();
const refFileData = useRef();
const processBlob = (blobData: Blob) => {
doxLib.renderAsync(blobData, refDom.current, null, {
// inWrapper: '',
// ignoreWidth: '',
// ignoreHeight: '',
// ignoreFonts: '',
// breakPages: true,
// debug: '',
// experimental: '',
// className: '',
// trimXmlDeclaration: '',
// renderHeaders: true,
// renderFooters: true,
// renderFootnotes: '',
// renderEndnotes: '',
// ignoreLastRenderedPageBreak: true,
// useBase64URL: '',
// useMathMLPolyfill: '',
// renderChanges: true,
});
};
const initFile = async () => {
const fileUrl = await getFileData(props.file?.fileId);
const blobData = await getBlobData(fileUrl);
refFileData.current = blobData;
processBlob(refFileData.current);
};
useEffect(() => {
initFile();
}, []);
return (
<div
style={{
border: '1px solid #eee',
height: '70vh',
overflowY: 'auto',
}}
ref={refDom}
></div>
);
};
视频预览 (react-player)
/** 视频预览 */
import ReactPlayer from 'react-player';
const RenderVideo = (props: { file: any }) => {
const [url, setUrl] = useState('');
const initFile = async () => {
const blobData = await getFileData(props.file?.fileId);
setUrl(blobData);
};
useEffect(() => {
initFile();
}, []);
return (
<ReactPlayer
width="100%"
height={'70vh'}
controls={true}
playing
url={url}
/>
);
};
文本预览
/** 文本预览 */
const RenderText = (props: { file: any }) => {
const [text, setText] = useState('');
const getText = async (blobData: Blob) => {
return new Promise((resolve, reject) => {
try {
const reader = new FileReader();
reader.readAsText(blobData, 'UTF-8');
reader.onload = function (e) {
const result = e.target.result;
resolve(result);
};
} catch {
reject();
}
});
};
const initFile = async () => {
const textUrl = await getFileData(props.file?.fileId);
const blobData = await getBlobData(textUrl);
const textVal = await getText(blobData).catch(() => {
setText('预览失败,请下载文件到本地后查看!');
});
setText(textVal);
};
useEffect(() => {
initFile();
}, []);
return (
<div style={{ height: '100%', whiteSpace: 'pre-wrap', overflow: 'auto' }}>
{text}
</div>
);
};
excel预览 (xlsx)
import * as XLSX from 'xlsx';
const RenderXlsx = (props: { file: any }) => {
const refDom = useRef();
const [loading, setLoading] = useState(false);
const refWorkbookIns = useRef<XLSX.WorkBook>();
const [sheets, setSheets] = useState<string[]>([]);
const [sheetIndex, setSheetsIndex] = useState(0);
// 表头
const headers = useMemo<string[]>(() => {
const sheet = sheets[sheetIndex];
const colHeader = XLSX.utils.sheet_to_json(
refWorkbookIns.current?.Sheets?.[sheet],
{
header: 1,
},
)[0];
return colHeader || [];
}, [sheetIndex, refWorkbookIns.current]);
// 表头格式化提供antd使用
const columns = headers?.map((kVal: string) => {
return {
title: kVal,
dataIndex: kVal,
};
});
// 表格数据
const data = useMemo(() => {
const sheet = sheets[sheetIndex];
if (!refWorkbookIns.current || !sheet) {
return [];
}
return XLSX.utils.sheet_to_json(refWorkbookIns.current?.Sheets?.[sheet]);
}, [refWorkbookIns.current, sheetIndex]);
const processBlob = (blobData: ArrayBuffer) => {
refWorkbookIns.current = XLSX.read(blobData, { type: 'array' });
// // 获取所有 Sheet
const sheets = refWorkbookIns.current.SheetNames || [];
setSheets(sheets);
};
const initPage = (file: File) => {
const reader = new FileReader();
reader.onload = (e: any) => {
setLoading(false);
const blob = new Uint8Array(e.target.result);
processBlob(blob);
};
reader.readAsArrayBuffer(file);
};
const items: TabsProps['items'] = sheets?.map((sName, index) => {
return {
key: index + '',
label: sName,
children: null,
};
});
const initFile = async () => {
const fileUrl = await getFileData(props.file?.fileId);
const blobData = await getBlobData(fileUrl);
initPage(blobData);
};
useEffect(() => {
setLoading(true);
initFile();
}, []);
return (
<div
style={{
height: '640px',
overflowY: 'auto',
}}
ref={refDom}
>
<div className="h-full flex flex-col">
<Tabs
defaultActiveKey="0"
items={items}
onChange={(val: string) => {
setSheetsIndex(val);
}}
/>
<div className="flex-1 overflow-auto">
<Table
size="small"
dataSource={data}
scroll={{ y: 530, x: 'max-content' }}
pagination={false}
columns={columns}
loading={loading}
></Table>
</div>
</div>
</div>
);
};
备注
当然还有其他的在线预览的付费方案,可推荐ali云方案