vue移动端文件预览
- 前言:
- 整体思路:
- 具体实现:
- 一、外壳(vViews.vue):
- 二、文件预览:
- 1.图片预览:
- 2.pdf文件预览:
- 3.docx文件预览:
- 4.excel表格预览(xls、xlsx格式表格):
- 5.txt文件预览:
- 三、补充外壳文件css样式:
- 四、js文件补充:
整体思路:
- 实现在不下载的情况下预览附件内容功能;
- 使用不同组件分别实现不同文件类型预览功能。目前实现功能:图片、pdf文件、docx、excel表格(xls、xlsx格式表格)、txt文件预览;
- 样式使用less;
具体实现:
一、外壳(vViews.vue):
将各个组件进行整合,在页面中引用时更加方便,项目使用了vant组件库,在之前的文章中描述了如何安装,这里不做赘述。
<template>
<div class="viewBox" v-if="isShow">
<!-- 关闭按钮 -->
<van-icon class="closeBtn" name="cross" @click="isShow = false" />
</div>
</template>
export default {
props: {
datas: {},
type: {}
},
data() {
return {
isShow: false
};
},
methods: {
/**
* 处理展示数据
*/
showFile(newVal) {
}
}
};
<style lang="less" scoped>
.viewBox {
width: 100vw;
height: 100vh;
overflow: hidden;
background: #ffffff;
position: absolute;
top: 0;
left: 0;
z-index: 2000;
.closeBtn {
position: absolute;
top: 10px;
right: 10px;
z-index: 10;
font-size: 24px;
}
}
</style>
在父组件中引用:
<template>
<div>
<!-- 附件列表 -->
<div v-for="(item, index) in fileList"
:key="index"
@click="showFile(item)">
{{item.file_name}}
</div>
<v-views ref="vViews" :datas="src" :type="type"></v-views>
</div>
</template>
import vViews from '@/components/vViews';
export default {
components: { vViews },
data() {
return {
fileList: [], // 附件列表
src: '', // 传参
type: '', // 附件类型
fileObj: {} // 存放已获取的数据流
};
},
methods: {
/**
* 点击查看附件
*/
showFile(item) {
// 获取允许预览文件类型
const typeObj = {
'.docx': 'word',
'.pdf': 'pdf',
'.jpg': 'img',
'.png': 'img',
'.xls': 'excel',
'.xlsx': 'excel',
'.txt': 'txt',
};
this.type = typeObj[item.file_type] || '';
// 已经调用过接口获取到了文件流数据
if (this.fileObj[item.file_name]) {
this.openFile(this.fileObj[item.file_name]);
} else {
this.downloadFile(item);
}
},
/**
* 打开文件预览组件
*/
openFile(datas) {
this.src = datas.fileBinary;
this.$nextTick(()=> {
this.$refs.vViews.isShow = true;
this.$refs.vViews.showFile(this.src);
});
},
/**
* 下载接口
*/
downloadFile(item) {
// 判断是否允许预览
if (this.type !== '') {
const res = {...}; // 这里指从后台接口获取的数据
this.fileObj[res.data.fileName] = res.data;
this.openFile(res.data);
} else {
const types = item.file_type.substr(1);
this.$toast(`${types}文件类型暂不支持预览`);
}
},
}
};
二、文件预览:
1.图片预览:
因为我的项目安装了vant,所以这里使用了vant的ImagePreview图片预览组件,在外壳(vViews.vue)页面里进行导入使用。
import { ImagePreview } from 'vant';
export default {
methods: {
showFile(newVal) {
if (this.type === 'img') {
// 图片预览,这里是后台返回了base64的数据,处理成了blob格式
const val = this.$base64ToBlob(newVal, 'image/jpeg');
this.newSrc = window.URL.createObjectURL(val);
// vant自带图片预览组件
const that = this;
ImagePreview({
images: [this.newSrc],
showIndex: false,
onClose() {
that.isShow = false;
},
});
}
}
}
};
2.pdf文件预览:
预览pdf文件,并实现展示当前、总页数及前后翻页功能。
安装vue-pdf依赖
npm install --save vue-pdf
<template>
<div class="viewBox" v-if="isShow">
<!-- 关闭按钮 -->
<van-icon class="closeBtn" name="cross" @click="isShow = false" />
<!-- pdf组件 -->
<div class="pdf-box" v-if="type === 'pdf'">
<div class="pdf_btn" v-if="pageTotalNum > 1">
<van-button plain
color="#07AC7F"
@click="prePage"
>上一页</van-button>
{{pageNum}}/{{pageTotalNum}}
<van-button plain
color="#07AC7F"
@click="nextPage"
>下一页</van-button>
</div>
<pdf
:page="pageNum"
:src="newSrc"
@progress="loadedRatio = $event"
@num-pages="pageTotalNum=$event"
></pdf>
</div>
</div>
</template>
import pdf from 'vue-pdf'; // pdf
export default {
components: { pdf },
data() {
return {
pageNum: 1,
pageTotalNum: 1, // 总页数
newSrc: '', // 路径
};
},
methods: {
showFile(newVal) {
if (this.type === 'pdf') {
this.pageNum = 1;
this.pageTotalNum = 1;
// pdf预览
const val = this.$base64ToBlob(newVal, 'application/pdf');
const href = window.URL.createObjectURL(val);
this.newSrc = pdf.createLoadingTask(href);
}
},
/**
* 上一页
*/
prePage() {
let page = this.pageNum;
page = page > 1 ? page - 1 : this.pageTotalNum
this.pageNum = page;
},
/**
* 下一页
*/
nextPage() {
let page = this.pageNum;
page = page < this.pageTotalNum ? page + 1 : 1
this.pageNum = page;
},
}
};
3.docx文件预览:
安装mammoth依赖
npm install --save mammoth
<template>
<div class="viewBox" v-if="isShow">
<!-- 关闭按钮 -->
<van-icon class="closeBtn" name="cross" @click="isShow = false" />
<!-- docx组件 -->
<div class="word-box" v-if="type === 'word'" ref="docPreview"></div>
</div>
</template>
import mammoth from 'mammoth'; // word文档
export default {
methods: {
showFile(newVal) {
if (this.type === 'word') {
// word文档预览,只能预览docx
const val = this.$base64ToBlob(newVal, 'application/msword');
mammoth
.convertToHtml({ arrayBuffer: val })
.then(this.displayResult)
.done();
}
},
/**
* word----docx预览
*/
displayResult(result) {
this.$refs.docPreview.innerHTML = result.value || '';
},
}
};
4.excel表格预览(xls、xlsx格式表格):
实现表格预览,实现多表格切换显示。
安装xlsx依赖
npm install xlsx --save
<template>
<div class="viewBox" v-if="isShow">
<!-- 关闭按钮 -->
<van-icon class="closeBtn" name="cross" @click="isShow = false" />
<!-- 表格组件 -->
<div class="table-box" v-if="type === 'excel'">
<van-tabs class="table-tab"
v-if="sheetNames.length"
title-active-color="#07AC7F"
color="#07AC7F"
@click="clickTab">
<van-tab
v-for="(item, index) in sheetNames"
:key="index"
:name="item"
:title="item"></van-tab>
</van-tabs>
<div class="tableBox" ref="excPreview"></div>
</div>
</div>
</template>
import XLSX from 'xlsx'; // excel表格
export default {
data() {
return {
sheetNames: [],
wsObj: {}
};
},
methods: {
showFile(newVal) {
if (this.type === 'excel') {
// excel预览
this.sheetNames = []; // 重置
const wb = XLSX.read(newVal, { type: 'base64' });
this.sheetNames = [...wb.SheetNames]; // 数组
this.wsObj = { ...wb.Sheets };
this.changeExcel(this.sheetNames[0]);
}
},
/**
* 切换表格
*/
clickTab(name) {
this.changeExcel(name);
},
/**
* 处理excel表格
*/
changeExcel(item) {
// 获取当前选中表格对象
const ws = this.wsObj[item];
const keyArr = Object.keys(ws) || [];
const HTML = keyArr.length > 1 ? XLSX.utils.sheet_to_html(ws)
: '<html><head><meta charset="utf-8"/>' +
'<title>SheetJS Table Export</title></head><body><div class="myTable">暂无数据</div></body>' +
'</html>';
this.$nextTick(()=> {
this.$refs.excPreview.innerHTML = HTML;
// 获取表格dom元素
const tables = this.$refs.excPreview.children[2];
// 添加完毕后 通过空格将数组组装为字符串
tables.className = 'myTable';
})
},
}
};
5.txt文件预览:
<template>
<div class="viewBox" v-if="isShow">
<!-- 关闭按钮 -->
<van-icon class="closeBtn" name="cross" @click="isShow = false" />
<div class="txt-box" v-if="type === 'txt'" ref="txtPreview"></div>
</div>
</template>
export default {
methods: {
showFile(newVal) {
if (this.type === 'txt') {
// txt
const val = this.$base64ToBlob(newVal, 'text/plain');
const reader = new FileReader();
const that = this;
reader.readAsText(val);
reader.onload = function () {
that.$refs.txtPreview.innerHTML = reader.result || '';
}
}
}
}
};
三、补充外壳文件css样式:
<style lang="less" scoped>
.pdf-box, .word-box, .table-box, .txt-box {
width: 100vw;
height: 100vh;
}
.pdf-box {
.pdf_btn {
padding: 0 10px;
height: 44px;
box-sizing: border-box;
display: flex;
justify-content: center;
align-items: center;
/deep/.van-button {
margin: 0 2px;
height: 24px;
&:nth-child(1) {
border-radius: 15px 0 0 15px;
}
&:nth-child(2) {
border-radius: 0 15px 15px 0;
}
}
}
>span {
width: 100%;
height: calc(100% - 44px);
}
}
.word-box {
padding: 40px 15px 15px;
box-sizing: border-box;
overflow: auto;
}
.table-tab {
width: calc(100% - 44px);
}
// 考核模块table样式
.tableBox {
width: 100vw;
height: calc(100vh - 44px);
overflow: auto;
// 表格边框
@table-border: 1px solid #929292;
// 表格
/deep/ table.myTable {
width: auto;
height: auto;
color: #333333;
// 合并边框
border-collapse: collapse;
border: @table-border;
// th,td
th, td {
// 禁止换行
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
background: #ffffff;
padding: 10px;
border: @table-border;
}
th {
// 正常粗细
font-weight: normal;
// 表头信息居左显示
&.th-info {
text-align: left;
span {
// 表头信息样式优化
margin-right: 30px;
}
}
}
// 表格内容居中
td {
text-align: center;
}
}
/deep/ div.myTable {
text-align: center;
margin: 30% auto;
}
}
.txt-box {
padding: 40px 15px 15px;
box-sizing: border-box;
overflow: auto;
}
</style>
四、js文件补充:
function base64ToBlob(base64, mimetype, slicesize) {
let num = 0;
if (!window.atob || !window.Uint8Array) {
return null;
}
mimetype = mimetype || '';
slicesize = slicesize || 512;
const bytechars = atob(base64);
const bytearrays = [];
for (let a = 0; a < bytechars.length; a += slicesize) {
const slice = bytechars.slice(a, a + slicesize);
const bytenums = new Array(slice.length);
for (var i = 0; i < slice.length; i++) {
bytenums[i] = slice.charCodeAt(i);
}
const bytearray = new Uint8Array(bytenums);
bytearrays[bytearrays.length] = bytearray;
}
return new Blob(bytearrays, {type: mimetype});
}