用最简单的使用方式,实现大多数类型资源的预览。
IPreview.previewUuid('xxxx-xxxx-xxxx-xxxx');
简介
一行代码实现预览 是我在公司内搭建的前端组件库中的一个功能。目的是解决预览方式不统一、预览不同类型资源困难、预览实现代码混乱的问题。这个功能从提出到落地应用,极大提高了我的开发效率(更多摸鱼时间)!
tips: 需要注意的是,这个功能依赖于云对象OSS存储,这也是大多数企业存储文件的方式,个人项目需要根据实现原理另行封装改造。
预览演示
此处为我部署在公司内网的文档站点,演示各类资源的预览效果。
实现原理
这里用简单代码展示原理,方便理解。实际开发中会进行大量优化,以及调整预览UI、预览功能等等。
3.1 根据uuid获取url
文件存储服务依赖uuid来区分文件,避免同名文件覆盖问题。一般在公司前端基建中已经封装好了工具函数,可以通过uuid获取文件地址。类似下面这种格式:
export async function getUrlByUuid (uuid: string): Promise<string> {
// ...
}
3.2 通过url计算预览信息
从云存储厂商返回的url中,我们能计算出很多信息。我总结了下有这些:fileName、fileExt、fileType,将它们统一放在名为预览信息的对象里。
interface PreviewInfo {
url: string; // oss文件地址
fileName: string; // 文件名称
fileExt: string; // 文件后缀 (例如:"doc")
fileType: 'img' | 'file' | 'video'; // 文件类型
}
export async function getPreviewInfoFromUrl (url: string): PreviewInfo {
// 根据url计算预览信息
// ...
}
3.3 预览图片
为了用于预览时缩放、移动图片的功能,我选择基于antd的Image组件进行封装,并通过函数调用的方式预览。
图片预览模板组件:
function PreviewImage ({src, onClose}) {
return (
<Image
preview={{
src,
visible: true,
onVisibleChange: value => {
if (!value) {
onClose?.()
}
}
}}
/>
)
}
预览图片:
// React 16
type Unmount = () => void;
// 打开预览弹窗
function openPreview (Template: React.FC, previewInfo: PreviewInfo): Unmount {
let div = document.createElement('div');
let el = React.createElement(Template, {
src: previewInfo.url,
onClose: unmount
})
document.body.appendChild(div);
// 将React元素渲染到div,让预览组件生效
ReactDOM.render(el, div);
// 卸载
function unmount () {
if (!div) return;
ReactDOM.unmountComponentAtNode(div);
div.remove();
div = null;
el = null;
}
return unmount;
}
// 预览图片
function previewImgUrl (previewInfo: PreviewInfo) {
openPreview(PreviewImage, previewInfo)
}
3.4 预览文件
预览文件这一步需要注意的是:需要获取可预览的url地址,否则文件有些会直接下载,例如doc。以doc文件可预览地址举例,需要公司事先拥有预览文件的在线网址。如果没有的话,需要去搭建一个类似的站点提供服务,方便后期一处维护所有项目生效。类似于这样的方式:
https://my.previewfile.com?url=【encodeURIComponent(oss地址)】
预览Iframe模板组件:
import { Modal } from 'antd'
function PreviewIframe ({src, onClose}) {
const [visible, setVisible] = useState(true);
return (
<Modal
visible={visible}
title="预览文件"
afterClose={onClose}
onCancel={() => setVisible(false)}
footer={null}
>
<iframe src={src} style={{width: '80vw',height: '80vh'}}/>
</Modal>
)
}
预览文件
// React 16
function previewFileUrl (previewInfo: PreviewInfo): Unmount {
return openPreview(PreviewIframe, previewInfo)
}
3.5 预览视频
预览视频,我这里选择的是 aliplayer-react,将其封装成视频弹窗模板。
预览视频模板组件:
import { Modal } from 'antd';
import Player from 'aliplayer-react';
// Video默认配置项
const INITIAL = {
source: '',
width: '100%',
height: '100%',
autoplay: true,
isLive: false,
rePlay: false,
playsinline: true,
preload: true,
controlBarVisibility: 'hover',
useH5Prism: true,
components: [
{
name: 'RateComponent',
type: Player.components.RateComponent,
},
],
};
function PreviewVideo ({src, onClose}) {
const [visible, setVisible] = useState(true);
// 配置添加视频地址
const videoConfig = useMemo(() => {
return {
...INITIAL,
source: src
}
}, [])
return (
<Modal
visible={visible}
title="预览文件"
afterClose={onClose}
onCancel={() => setVisible(false)}
footer={null}
>
<Player config={videoConfig} />
</Modal>
)
}
预览文件
// React 16
function previewVideoUrl (previewInfo: PreviewInfo) {
openPreview(PreviewVideo, previewInfo)
}
previewUuid 代码实现
class Preview {
// 其他功能
// ...
// 预览文件oss地址
public async previewUuid (uuid: string) {
const url = await getUrlByUuid(uuid);
return this.previewUrl(url)
}
// 预览文件uuid
public async previewUrl (ossUrl: string) {
const previewInfo = await getPreviewInfoFromUrl(ossUrl);
// 预览不同文件
switch (previewInfo.fileType) {
case 'img': // 预览图片
previewImgUrl(previewInfo)
break;
case 'file': // 预览文件
previewFileUrl(previewInfo)
break;
case 'video': // 预览视频
previewVideoUrl(previewInfo)
break;
default:
message.warn(`.${previewInfo.fileExt}格式文件暂不支持预览!`)
break;
}
}
}