在项目开发中,遇到很多次有关文件的需求,如不同文件类型的文件上传、文件下载、文件预览。文件上传在https://cloud.tencent.com/developer/article/2123591中有相关大文件分片上传、断点续传及秒传的介绍;文件下载在https://qkongtao.cn/?p=560#h2-0的第14个方法中有下载的工具方法介绍;各种文件的预览在项目中用的也比较频繁,大多数情况下的文件预览都会用第三方的服务或者后端服务进行实现,但是也有些情况适合纯web端实现各种文件的预览,本次就记录一下使用纯web端实现各种文档文件的预览,并且全部封装成单独的组件,开箱即用。
如果使用第三方服务,有以下的方案:
使用第三方的服务当然效果很好,但是成本更高,实现起来也没有那么灵活。
本次实现的文档预览的类型有:docx, xlsx, pptx, pdf,以及纯文本、代码文件和各种图片、视频格式的在线预览
纯web端文档预览项目在线地址:http://file-viewer.qkongtao.cn/
Office文档文件包括常见的docx、excel、pdf三种文件的预览,当然还有PPT文件预览,但是ppt使用纯前端实现预览效果不是很好,正确的做法一般会讲ppt文件在服务端转换成PDF文件后再进行预览。
本次的文档预览使用了vue-office组件库,安装方式如下:
//docx文档预览组件
npm install @vue-office/docx
//excel文档预览组件
npm install @vue-office/excel
//pdf文档预览组件
npm install @vue-office/pdf
使用vue-office组件库的pdf组件
安装vue-office组件:npm install @vue-office/pdf
实现代码如下:
<template>
<vue-office-pdf :src="pdf" @rendered="rendered" />
</template>
<script>
//引入VueOfficePdf组件
import VueOfficePdf from "@vue-office/pdf";
export default {
components: {
VueOfficePdf,
},
props: {
pdf: {
type: String,
default:
"http://qncdn.qkongtao.cn/lib/teamadmin/files/%E6%B7%B1%E5%9C%B3-xx-Java%E9%AB%98%E7%BA%A7.pdf",
},
},
data() {
return {};
},
methods: {
rendered() {
console.log("渲染完成");
},
},
};
</script>
实现效果如下:
在线预览:http://file-viewer.qkongtao.cn/pdf
使用 vue-pdf 插件来实现
安装 vue-pdf 插件:npm install --save vue-pdf
实现代码如下:
<template>
<div style="background-color: #fff">
<div style="margin-bottom: 15px; text-align: center">
<span>当前第{{ pdfpage }}页 / 共{{ pageCount ? pageCount : 0 }}页</span>
<span
style="margin-left: 20px"
>前往第
<el-input
v-model="num"
:oninput="
'if(!/^[0-9]+$/.test(value)) value=value.replace(/\\D/g,\'\');if(value>' +
pageCount +
')value=' +
pageCount +
';if(value<0)value=1'
"
style="width: 55px; display: inline-block"
@blur="goPage"
/>
页</span>
<el-button
type="primary"
size="small"
style="margin-left: 20px"
@click.stop="prevIoUsPage"
>
上一页
</el-button>
<el-button type="primary" size="small" @click.stop="nextPage">
下一页
</el-button>
<el-button size="mini" @click.stop="zoomA">放大</el-button>
<el-button
size="mini"
style="margin-left: 5px"
@click.stop="zoomB"
>缩小</el-button>
<span style="margin-left: 5px">大小 {{ parseInt(scale * 100) }}%</span>
</div>
<div>
<pdf
:src="src"
:page="pdfpage"
:style="scaleFun"
:class="pageCount ? 'load' : 'load_pdf'"
@num-pages="loading($event)"
@page-loaded="pdfpage = $event"
/>
<div v-if="!pageCount" class="loading">
<img src="../../assets/images/loading.gif" alt="">
<span>加载中...</span>
</div>
</div>
</div>
</template>
<script>
import pdf from 'vue-pdf'
export default {
components: {
pdf
},
props: {
src: {
type: String,
require: true,
default:
'http://qncdn.qkongtao.cn/lib/teamadmin/files/%E6%B7%B1%E5%9C%B3-xx-Java%E9%AB%98%E7%BA%A7.pdf'
}
},
data() {
return {
// PDF预览
pdfpage: 1,
pageCount: 0,
num: 1,
scale: 1 // 缩放比例
}
},
computed: {
scaleFun(index) {
// 缩放
var scale = this.scale
return `width: ${scale * 100}% !important`
}
},
mounted() {},
methods: {
loading(event) {
this.pageCount = event
},
// PDF改变页数
prevIoUsPage() {
var p = this.pdfpage
p = p > 1 ? p - 1 : this.pageCount
this.pdfpage = p
},
nextPage() {
var p = this.pdfpage
p = p < this.pageCount ? p + 1 : 1
this.pdfpage = p
},
goPage() {
this.pdfpage = parseInt(this.num)
},
zoomA() {
this.scale += 0.1
},
zoomB() {
this.scale -= 0.1
}
}
}
</script>
<style lang="scss" scoped>
/deep/ .el-input__inner {
text-align: center;
}
.pdf {
display: inline-block;
width: 100%;
// height: 100%;
transform-origin: 0 0;
}
.load_pdf {
display: none !important;
}
.loading {
width: 100%;
margin-top: 25%;
margin-bottom: 20%;
text-align: center;
img {
width: 20%;
}
span {
display: block;
text-align: center;
margin-top: 30px;
margin-bottom: 30px;
}
}
</style>
由于实在项目中用到了,所以加了一下翻页、缩放的功能
实现效果如下:
使用vue-office组件库的docx组件
安装 vue-office 插件:npm install @vue-office/docx
实现代码如下:
<template>
<div>
<vue-office-docx
:src="docx"
style="height: 100%; margin: 0; padding: 0"
@rendered="rendered"
/>
</div>
</template>
<script>
//引入VueOfficeDocx组件
import VueOfficeDocx from "@vue-office/docx";
//引入相关样式
import "@vue-office/docx/lib/index.css";
export default {
components: {
VueOfficeDocx,
},
props: {
docx: {
type: String,
default:
"http://qncdn.qkongtao.cn/lib/teamadmin/files/Hadoop2.7.1%E4%BC%AA%E5%88%86%E5%B8%83%E5%BC%8F%E9%9B%86%E7%BE%A4%E5%AE%89%E8%A3%85%E6%96%87%E6%A1%A3.docx", //设置文档网络地址,可以是相对地址
},
},
data() {
return {};
},
methods: {
rendered() {
console.log("渲染完成");
},
},
};
</script>
实现效果如下:
在线预览:http://file-viewer.qkongtao.cn/doc
使用vue-office组件库的excel组件
安装vue-office插件:npm install @vue-office/excel
实现代码如下:
<template>
<vue-office-excel
:src="excel"
@rendered="renderedHandler"
@error="errorHandler"
style="height: calc(100vh - 90px)"
/>
</template>
<script>
//引入VueOfficeExcel组件
import VueOfficeExcel from "@vue-office/excel";
//引入相关样式
import "@vue-office/excel/lib/index.css";
export default {
components: {
VueOfficeExcel,
},
props: {
excel: {
type: String,
default:
"http://qncdn.qkongtao.cn/lib/teamadmin/files/2021%E5%B1%8A%E5%85%A8%E5%9B%BD%E5%90%84%E5%9C%B0%E6%B4%BE%E9%81%A3%E5%9C%B0%E5%9D%80.xlsx", //设置文档地址
},
},
data() {
return {};
},
methods: {
renderedHandler() {
console.log("渲染完成");
},
errorHandler() {
console.log("渲染失败");
},
},
};
</script>
实现代码如下:
在线预览:http://file-viewer.qkongtao.cn/excel
PPT文档预览纯前端实现起来比较困难,效果也不怎么好,建议可以先在服务端转换成PDF文档,使用PDF文档预览的效果比较好。
纯web实现方案:
需要安装以下插件:
"core-js": "^3.3.2",
"d3": "^3.5.17",
"dimple": "git+https://gitee.com/mirrors/dimple.git#2.3.0",
"jszip": "^3.10.0",
具体实现代码直接查看gitee:https://gitee.com/qkongtao/document-preview-project/tree/master/src/components/pptx
实现效果如下:
可以看到很多图片样式信息都丢失了,所以还是直接使用第三方的
可以尝试XDOC文档的:https://view.xdocin.com/view?src=https://upyun.qkongtao.cn/others/document/C006.pptx
他的实现方案是后台转成PDF然后再预览。
文本文件预览使用了vue-codemirror插件
安装vue-codemirror插件:npm install vue-codemirror@4.0.6 --save
在main.js中引入插件:
// 引入jshint用于实现js自动补全提示
import jshint from "jshint";
window.JSHINT = jshint.JSHINT;
// 引入代码编辑器
import {
codemirror
} from "vue-codemirror";
import "codemirror/lib/codemirror.css";
Vue.use(codemirror);
实现代码如下:
在调用编辑器的插件里面加入了一些小功能:
实现效果如下:
在线预览:http://file-viewer.qkongtao.cn/code
图片文件预览可以直接使用img标签,或者用UI库的图片标签,如 el-image等,但是这种使用起来功能没有那么多,并且灵活性也不是很高,这次实现图片预览使用了v-viewer插件。
安装插件:npm install v-viewer --save
在main.js中挂载插件
import VueViewer from 'v-viewer';
import 'viewerjs/dist/viewer.css'
Vue.use(VueViewer, {
defaultOptions: {
// 自定义默认配置
zIndex: 9999,
inline: false, // 默认值:false。启用内联模式。
button: true, // 右上角关闭按钮
navbar: false, // 指定导航栏(图片组)的可见性。
title: true, //指定标题的可见性和内容
toolbar: true, // 指定工具栏及其按钮的可见性和布局
tooltip: true, //放大或缩小时显示带有图像比率(百分比)的工具提示。
movable: true, // 启用以移动图像。
zoomable: true, // 启用以缩放图像。
rotatable: true, // 启用以旋转图像
scalable: true, // 用以反转图像。
transition: false, // 为某些特殊元素启用CSS3转换。
fullscreen: false, // 启用以在播放时请求全屏。
keyboard: true, //启用键盘支持。
url: 'src', //默认值:"src"。定义获取原始图像URL以供查看的位置。
},
});
功能可以自己选择开关:
图片预览组件实现代码如下:
<template>
<div>
<viewer>
<img alt="图片" :src="src" class="image" />
</viewer>
</div>
</template>
<script>
export default {
props: {
src: {
type: String,
default: "http://upyun.qkongtao.cn/chevereto/2022/08/30/onepiece.png",
},
},
data() {
return {};
},
};
</script>
<style scoped>
.image {
display: block;
width: auto;
width: 600px;
margin: 100px auto;
}
</style>
实现效果如下:
在线预览(可点击):http://file-viewer.qkongtao.cn/img
本次视频文件预览尝试使用了三种插件实现:
这三种开源的播放器功能比较全,样式也比较好看,可以适用于大部分视频播放的场景,可以真正的告别video标签了。
本次实现播放器父组件向子组件传参结构示例:
video: {
poster:
"https://upyun.qkongtao.cn/others/video/%E8%8D%89%E5%B8%BD%E4%B8%80%E4%BC%99%E6%82%AC%E8%B5%8F%E4%BB%A4%E4%BC%A0%E9%81%8D%E5%85%A8%E4%B8%96%E7%95%8C.mp4.jpg",
thumbnailUrl:
"https://upyun.qkongtao.cn/others/video/%E8%8D%89%E5%B8%BD%E4%B8%80%E4%BC%99%E6%82%AC%E8%B5%8F%E4%BB%A4%E4%BC%A0%E9%81%8D%E5%85%A8%E4%B8%96%E7%95%8C.mp4.vtx",
src: "https://upyun.qkongtao.cn/AList/%E8%8D%89%E5%B8%BD%E4%B8%80%E4%BC%99%E6%82%AC%E8%B5%8F%E4%BB%A4%E4%BC%A0%E9%81%8D%E5%85%A8%E4%B8%96%E7%95%8C.mp4",
},
官方文档:https://player.alicdn.com/aliplayer/presentation/index.html?type=memoryPlay
安装方法:在项目中引入阿里云播放器
<!-- 引用阿里云播放器 -->
<link rel="stylesheet" href="https://g.alicdn.com/de/prismplayer/2.8.2/skins/default/aliplayer-min.css" />
<script charset="utf-8" type="text/javascript" src="https://g.alicdn.com/de/prismplayer/2.8.2/aliplayer-min.js">
封装组件实现代码:
<template>
<!--播放器-->
<div
class="prism-player"
id="J_prismPlayer"
style="margin-left: auto; margin-right: auto"
></div>
</template>
<script>
export default {
props: {
//组件需要的参数
video: Object,
},
data() {
return {};
},
created() {
//console.log("组件成功")
},
mounted() {
this.initPlayer();
},
methods: {
initPlayer() {
//console.log(this.video)
var player = new Aliplayer(
{
id: "J_prismPlayer", // id选择器
source: this.video.src, //视频地址
width: "800px",
height: "600px",
//cover: 'http://images.qkongtao.cn/images/2021/08/15/t012cde4a5058c156b7.jpg', // 封面
qualitySort: "asc", // 清晰度排序
mediaType: "video", // 返回音频还是视频
autoplay: false, // 自动播放
isLive: false, // 直播
rePlay: false, // 循环播
preload: true,
controlBarVisibility: "hover", // 控制条的显示方式:鼠标悬停
useH5Prism: true, // 播放器类型:html5
thumbnailUrl: this.video.thumbnailUrl,
// 允许匿名跨域访问属性
extraInfo: {
crossOrigin: "anonymous",
},
skinLayout: [
{ name: "bigPlayButton", align: "blabs", x: 30, y: 80 },
{ name: "H5Loading", align: "cc" },
{ name: "errorDisplay", align: "tlabs", x: 0, y: 0 },
{ name: "infoDisplay" },
{ name: "tooltip", align: "blabs", x: 0, y: 56 },
{ name: "thumbnail" },
{
name: "controlBar",
align: "blabs",
x: 0,
y: 0,
children: [
{ name: "progress", align: "blabs", x: 0, y: 44 },
{ name: "playButton", align: "tl", x: 15, y: 12 },
{ name: "timeDisplay", align: "tl", x: 10, y: 7 },
{ name: "fullScreenButton", align: "tr", x: 10, y: 12 },
// { name: "subtitle", align: "tr", x: 15, y: 12 },
// { name: "setting", align: "tr", x: 15, y: 12 },
{ name: "volume", align: "tr", x: 5, y: 10 },
{ name: "snapshot", align: "tr", x: 5, y: 9 },
],
},
],
},
function (player) {
console.log("播放器创建成功");
}
);
// 绑定鼠标事件(暂停、播放)
var _video = document.querySelector("video");
_video.addEventListener("click", play);
player.on("play", function (e) {
_video.removeEventListener("click", play);
_video.addEventListener("click", pause);
});
player.on("pause", function (e) {
_video.removeEventListener("click", pause);
_video.addEventListener("click", play);
});
function play() {
if (player) player.play();
document.getElementsByClassName("prism-big-play-btn")[0].style.display =
"none";
}
function pause() {
if (player) player.pause();
document.getElementsByClassName("prism-big-play-btn")[0].style.display =
"block";
}
/* h5截图按钮, 截图成功回调 */
player.on("snapshoted", function (data) {
var pictureData = data.paramData.base64;
var downloadElement = document.createElement("a");
downloadElement.setAttribute("href", pictureData);
var fileName = "Aliplayer" + Date.now() + ".png";
downloadElement.setAttribute("download", fileName);
downloadElement.click();
pictureData = null;
});
},
},
};
</script>
<style scoped>
.prism-player {
margin: 0;
padding: 0;
}
实现效果如下:
官方文档:https://h5player.bytedance.com/guide/
安装方法:npm install xgplayer --save
封装组件实现代码:
<template>
<div>
<div id="myPlayer"></div>
</div>
</template>
<script>
import Player from "xgplayer";
import "xgplayer/dist/index.min.css";
import Danmu from "xgplayer/es/plugins/danmu";
import "xgplayer/es/plugins/danmu/index.css";
export default {
props: {
//组件需要的参数
video: Object,
},
data() {
return {
player: null,
};
},
mounted() {
this.initXgplayer();
},
methods: {
initXgplayer() {
const config = {
// 播放器ID
id: "myPlayer",
width: "800px",
height: "500px",
// 视频链接
url: this.video.src,
playsinline: true,
// 自动播放
autoplay: false,
// 视频封面链接
poster: this.video.poster,
// 播放器插件
plugins: [Danmu],
// 流式布局
fluid: true,
// 初始音量
volume: 1,
// 倍数配置
playbackRate: [0.5, 1, 1.5, 2],
// 缩略图集
thumbnail: {
pic_num: 44,
width: 160,
height: 90,
col: 10,
row: 10,
urls: [this.video.thumbnailUrl],
},
// 显示下载按钮
download: true,
// 显示截图按钮
screenShot: true,
// 防止canvas toDataURL跨域画布污染
videoAttributes: {
crossOrigin: "anonymous",
},
/*********************** 弹幕配置start ***********************/
danmu: {
// 预设弹幕内容
comments: [
{
duration: 15000,
id: "2",
start: 3000,
txt: "长弹幕长弹幕长弹幕",
mode: "top",
style: {
//弹幕自定义样式
color: "#ff9500", //例:'#ff9500',
"font-size": "30px", // 例:'20px',
padding: "2px 11px", //例: 2px 11px',
},
},
{
duration: 15000,
id: "3",
start: 4000,
txt: "长弹幕长弹幕长弹幕",
mode: "bottom",
style: {
//弹幕自定义样式
color: "#ff9500", //例:'#ff9500',
"font-size": "40px", // 例:'20px',
padding: "2px 11px", //例: 2px 11px',
},
},
{
duration: 15000,
id: "4",
start: 5000,
txt: "长弹幕长弹幕长弹幕",
mode: "scroll",
style: {
//弹幕自定义样式
color: "#de1c31", //例:'#ff9500',
"font-size": "48px", // 例:'20px',
padding: "2px 11px", //例: 2px 11px',
},
},
{
duration: 15000,
id: "5",
start: 8000,
txt: "长弹幕长弹幕长弹幕",
mode: "scroll",
style: {
//弹幕自定义样式
color: "#813c85", //例:'#ff9500',
"font-size": "30px", // 例:'20px',
padding: "2px 11px", //例: 2px 11px',
},
},
],
area: {
start: 0,
end: 1,
},
// 不使用默认提供弹幕开关
closeDefaultBtn: false,
// 关闭弹幕初始化
defaultOff: true,
// 弹幕控制面板
panel: false,
},
/*********************** 弹幕配置end ***********************/
};
this.player = new Player(config);
},
},
};
</script>
<style scoped>
</style>
实现效果如下:
参考文档:https://cloud.tencent.com/developer/article/2134833
具体的实现方法可以参考上面的文章,项目中直接使用iframe嵌入播放器。
封装组件实现代码:
<template>
<div>
<iframe
:src="playerApi + src"
allowfullscreen="allowfullscreen"
mozallowfullscreen="mozallowfullscreen"
msallowfullscreen="msallowfullscreen"
oallowfullscreen="oallowfullscreen"
webkitallowfullscreen="webkitallowfullscreen"
width="800px"
height="500px"
frameborder="0"
style="player"
>
</iframe>
</div>
</template>
<script>
export default {
props: {
//组件需要的参数
video: Object,
},
data() {
return {
playerApi: "http://code.qkongtao.cn/video/player/?url=",
src: "",
};
},
mounted() {
this.src = this.video.src;
},
methods: {},
};
</script>
<style scoped>
.player {
margin-top: 30px;
}
</style>
实现效果如下:
音频文件预览的使用场景相对比较少,这里就简单的集成一下APlayer插件实现一下友好的音乐播放器。
APlayer官方文档:https://aplayer.js.org/#/zh-Hans/?id=%E5%AE%89%E8%A3%85
安装方法:npm install aplayer --save
封装组件实现代码
<template>
<div>
<div id="aplayer" class="aplayer"></div>
</div>
</template>
<script>
import APlayer from "aplayer";
import "aplayer/dist/APlayer.min.css";
export default {
props: {
musicList: {
type: Array,
default: [],
},
},
data() {
return {
myAPlayer: null,
};
},
mounted() {
this.initAPlayer();
},
methods: {
initAPlayer() {
const options = {
container: document.getElementById("aplayer"),
theme: "#e9e9e9",
audio: this.musicList,
};
const ap = new APlayer(options);
this.myAPlayer = ap;
},
},
};
</script>
<style scoped>
</style>
调用组件传入参数示例:
musicList: [
{
name: "新时代",
artist: "Ado",
url: "https://upyun.qkongtao.cn/wordpress/files/%E6%96%B0%E6%97%B6%E4%BB%A3.mp3",
cover:
"https://qkongtao.cn/file/music/pic/%E6%96%B0%E6%97%B6%E4%BB%A3.png",
},
{
name: "偏爱",
artist: "张芸京",
url: "https://qkongtao.cn/file/music/%E5%81%8F%E7%88%B1-%E5%BC%A0%E8%8A%B8%E4%BA%AC.mp3",
cover:
"https://qkongtao.cn/file/music/pic/我们的爱我不放手-张芸京.jpg",
},
{
name: "残酷月光",
artist: "林宥嘉",
url: "https://qkongtao.cn/file/music/%E6%AE%8B%E9%85%B7%E6%9C%88%E5%85%89%20%E6%9E%97%E5%AE%A5%E5%98%89.mp3",
cover:
"https://qkongtao.cn/file/music/pic/%E6%AE%8B%E9%85%B7%E6%9C%88%E5%85%89.png",
},
],
实现效果如下:
在线预览:http://file-viewer.qkongtao.cn/music
可以根据官方文档设置播放器场景样式。
上述的组件是本项目主要实现的功能,最后我将组件进行了整合一下,封装成一个文件上传、下载、预览的demo。
在线预览地址:http://file-viewer.qkongtao.cn/