附件预览组件
- 目录
-
- preview
- props
- event
- usage
- 效果图
- 组件源码
目录
preview
组件描述:附件预览组件。
适用场景:根据文件流前端预览。
props
props | type | default | explain |
---|---|---|---|
previewDownLoad | Boolean | true | 是否启用内部下载接口,默认启用 |
prop | Object | { | 配置字段 |
prewKey: ‘url’, | prewKey:预览接口发送的key | ||
name: ‘name’, | name:文件名 | ||
url: ‘url’ | url:文件下载接口发送的url | ||
} | |||
previewApi | Object | { | 预览接口,非必传,默认使用内置接口 |
url: , | url:请求路径 | ||
type: ‘post’ | type:请求方式 | ||
} | |||
downloadApi | Object | { | 下载接口,非必传,默认使用内置接口 |
url: , | url:请求路径 | ||
type: ‘get’ | type:请求方式 | ||
} |
event
event | default | explain |
---|---|---|
downLoad | file | 点击下载按钮时触发,当previewDownLoad为fasle时有效 |
usage
- 页面引入
<template>
<div>
<preview
ref="preview"
:downloadApi="{
url: '/purchaser/upload/download',
type: 'get'
}"
:previewApi="{
url: '/purchaser/file/preview',
type: 'post'
}"
:prop="{
prewKey: 'url',
name: 'name',
url: 'url'
}"></preview>
</div>
</template>
- 注册
import preview from 'fe-coms/components/business/preview/index.vue' //改成你自己的路径
- 使用
调用预览方法:
this.$refs['preview'].preview(file, fileList) // 参数说明:file 当前点击的文件 fileList文件集合
效果图
- 预览图片格式
- 预览pdf格式
- 无法预览文件
组件源码
<template>
<div
class="sy-preview"
v-if="visible"
>
<!-- 遮罩层 -->
<div class="sy-image-viewer__mask"></div>
<!-- 头部 -->
<div class="header">
<div class="file-title">{{imgItem.name}}</div>
<i
class="el-icon-circle-close"
@click="close()">
</i>
<i
class="el-icon-download"
@click="downloadClick()">
</i>
</div>
<!-- 文件预览区域 -->
<div
class="sy-image-viewer__canvas">
<div
v-for="(item, i) in imgBlobList"
:key="item">
<template
v-if="i === index">
<template
v-if="accept.indexOf(imgItem.type) > -1">
<img
v-if="imgItem.type !== 'pdf'"
:style="imgStyle"
:src="imgItem.src"
@error="handleImgError">
<embed v-else :src="imgItem.src" type="application/pdf" style="position:fixed;top:72px;left:0;right:0;bottom:0;" width="100%" height="100%">
</template>
</template>
</div>
<div
v-show="accept.indexOf(imgItem.type) === -1"
class="not-support__wapper">
<span class="link-box">
<i class="el-icon-link"></i>
</span>
<p class="text mt-10">我们不能预览该文件。</p>
<p class="text">您要先下文件以查看。</p>
<el-button class="mt-10" @click="downloadClick()" plain>下载</el-button>
</div>
</div>
<!-- 切换 上一个 下一个 -->
<span
class="image-viewer__btn image-viewer__prev"
@click="prev">
<i class="el-icon-arrow-left"></i>
</span>
<span
class="image-viewer__btn image-viewer__next"
@click="next">
<i class="el-icon-arrow-right"></i>
</span>
<!-- 放大缩小旋转 -->
<div
class="image-viewer__btn sy-image-viewer__actions"
v-if="imgItem.type !== 'pdf'">
<div class="sy-image-viewer__actions__inner">
<i class="el-icon-zoom-out" @click="handleActions('zoomOut')"></i>
<i class="el-icon-zoom-in" @click="handleActions('zoomIn')"></i>
<i :class="mode.icon" @click="toggleMode"></i>
<i class="el-icon-refresh-left" @click="handleActions('anticlocelise')"></i>
<i class="el-icon-refresh-right" @click="handleActions('clocelise')"></i>
</div>
</div>
</div>
</template>
<script>
import download from '../../../utils/download'
const Accept = ['jpg', 'png', 'pdf'] //后端支持的文件类型枚举
const Mode = {
CONTAIN: {
name: 'contain',
icon: 'el-icon-full-screen'
},
ORIGINAL: {
name: 'original',
icon: 'el-icon-c-scale-to-original'
}
}
const defaultApi = { //附件预览内置api
downloadApi: {
url: `/${window.fe.project}/upload/download`, // window.fe.project == `purchaser`
type: 'get' // type
},
previewApi: { //预览api
url: `/${window.fe.project}/file/preview`, // url
type: 'post' // type
}
}
export default {
name: 'preview',
data () {
return {
visible: false,
fileList: [],
imgBlobList: [],
imgItem: {
'src': null,
'type': '',
'name': '',
'raw': null,
'key': ''
},
index: 0,
mode: Mode.CONTAIN,
accept: Accept,
transform: {
scale: 1,
deg: 0,
offsetX: 0,
offsetY: 0,
enableTransition: false
}
}
},
props: {
prop: {
type: Object,
default: {
prewKey: 'url',
fileReviewBusinessCode: 'DEMO', //文件预览业务枚举code
bizCode: null, //业务code
name: 'name', //文件名称
url: 'url' //文件下载接口的url参数取值字段
}
},
downloadApi: { //下载api
type: Object,
default: defaultApi.downloadApi
},
previewApi: { //预览api
type: Object,
default: defaultApi.previewApi
},
previewDownLoad: { //是否启用预览附件组件的下载方式 默认启用
type: Boolean,
default: true
}
},
computed: {
imgStyle () {
const { scale, deg, offsetX, offsetY, enableTransition } = this.transform
const style = {
transform: `scale(${scale}) rotate(${deg}deg)`,
transition: enableTransition ? 'transform .3s' : '',
'margin-left': `${offsetX}px`,
'margin-top': `${offsetY}px`
}
if (this.mode === Mode.CONTAIN) {
style.maxWidth = style.maxHeight = '100%'
}
return style
}
},
methods: {
// 关闭弹窗
close () {
this.visible = false
},
// 预览
preview (file, fileList) {
this.fileList = fileList
this.imgBlobList = []
this.imgItemReset()
this.setPreviewData(file, fileList)
this.visible = true
},
// 组装预览的数据
setPreviewData (file, fileList) {
console.log(file, fileList)
fileList.forEach((element, i) => {
let nameIndex = element[this.prop.name].split('.')
let type = nameIndex[nameIndex.length - 1]
console.log(type, Accept)
let imgItem = {
'src': type !== 'pdf' && element.raw ? URL.createObjectURL(element.raw) : null, //预览图片的字段 前端用
'raw': element.raw || null, //文件流
'type': type, //文件类型 做区分用 不同类型预览方式不一样
'name': element[this.prop.name], //文件名称 传给后端也是这个
'key': element[this.prop.url] || '' //文件下载地址 传给后端也是这个
}
this.imgBlobList.push(imgItem)
console.log('imgBlobList', this.imgBlobList)
if (file.uid === element.uid) {
this.index = i
if (file.hasOwnProperty('raw')) { //有文件流 直接预览
this.imgItem = imgItem
console.log(this.imgItem)
} else { //否则 从服务器获取文件流
this.imgItem = imgItem
this.getFileBlob(file)
}
}
})
},
// 预览接口
async getFileBlob (file) {
let nameIndex = file[this.prop.name].split('.')
let type = nameIndex[nameIndex.length - 1]
if (this.accept.indexOf(type) === -1) return //不支持的类型 不走接口
let api = this.previewApi
let params = {
'key': file[this.prop.prewKey],
'fileReviewBusinessCode': this.prop.fileReviewBusinessCode || 'DEMO',
'bizCode': this.prop.bizCode || null
}
const loading = this.$loading({
lock: true,
text: '文件加载中,请稍等...',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
})
// 使用axios接受文件流
this.axios.post(api.url, params, {responseType: 'arraybuffer'})
.then((response) => {
loading.close()
if (response.status === 200) {
const blobType = (type === 'pdf' ? `application/pdf` : `image/${type}`)
const src = new Blob([response.data], { type: blobType })
this.imgItem.src = window.URL.createObjectURL(src)
this.imgBlobList[this.index].src = window.URL.createObjectURL(src)
this.imgBlobList[this.index].raw = response.data
}
})
.catch((error) => {
loading.close()
console.log(error)
})
},
// 放大 缩小 旋转
handleActions (action, options = {}) {
const { zoomRate, rotateDeg, enableTransition } = {
zoomRate: 0.2,
rotateDeg: 90,
enableTransition: true,
...options
}
const { transform } = this
switch (action) {
case 'zoomOut':
if (transform.scale > 0.2) {
transform.scale = parseFloat((transform.scale - zoomRate).toFixed(3))
}
break
case 'zoomIn':
transform.scale = parseFloat((transform.scale + zoomRate).toFixed(3))
break
case 'clocelise':
transform.deg += rotateDeg
break
case 'anticlocelise':
transform.deg -= rotateDeg
break
}
transform.enableTransition = enableTransition
},
// 图片重置 style transform
reset () {
this.transform = {
scale: 1,
deg: 0,
offsetX: 0,
offsetY: 0,
enableTransition: false
}
},
// 重置当前显示的图片
imgItemReset () {
this.imgItem = {
'src': null,
'type': '',
'name': '', //文件名称
'raw': null,
'key': ''
}
},
// 全屏模式切换
toggleMode () {
const modeNames = Object.keys(Mode)
const modeValues = Object.values(Mode)
const index = modeValues.indexOf(this.mode)
const nextIndex = (index + 1) % modeNames.length
this.mode = Mode[modeNames[nextIndex]]
this.reset()
},
// 图片加载失败
handleImgError (e) {
// this.loading = false
e.target.alt = '加载失败'
},
// 切换 上一个
prev () {
this.imgItemReset()
const len = this.imgBlobList.length
this.index = (this.index - 1 + len) % len
if (this.imgBlobList[this.index].src) {
this.imgItem = this.imgBlobList[this.index]
} else {
this.imgItem = this.imgBlobList[this.index]
this.getFileBlob(this.fileList[this.index])
}
},
// 切换 下一个
next () {
this.imgItemReset()
const len = this.imgBlobList.length
this.index = (this.index + 1) % len
if (this.imgBlobList[this.index].src) {
this.imgItem = this.imgBlobList[this.index]
} else {
this.imgItem = this.imgBlobList[this.index]
this.getFileBlob(this.fileList[this.index])
}
},
// 点击下载按钮
async downloadClick () {
if (this.previewDownLoad) { //使用组件内的下载方法下载文件
this.downloadAjax()
} else { //将文件向外部抛出 满足个性化下载需求
console.log('外部下载')
this.$emit('downLoad', this.fileList[this.index])
}
},
// 下载接口
async downloadAjax () {
let api = this.downloadApi
let params = {
'url': this.imgItem.key,
'name': this.imgItem.name
}
download(api.type, api.url, params)
}
}
}
</script>
<style lang="scss" scoped>
.sy-preview {
z-index: 999;
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
.mt-10 {
margin-top: 10px;
}
.sy-image-viewer__mask {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
opacity: .5;
background: #000;
z-index: -1;
}
.el-icon-circle-close {
font-size: 32px;
position: absolute;
top: 20px;
right: 20px;
color: #fff;
cursor: pointer;
}
.el-icon-download {
font-size: 32px;
position: absolute;
top: 20px;
right: 72px;
color: #fff;
cursor: pointer;
}
.image-viewer__btn {
position: absolute;
z-index: 1;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
border-radius: 50%;
opacity: .8;
cursor: pointer;
-webkit-box-sizing: border-box;
box-sizing: border-box;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.image-viewer__next,
.image-viewer__prev {
top: 50%;
-webkit-transform: translateY(-50%);
transform: translateY(-50%);
width: 44px;
height: 44px;
font-size: 24px;
color: #fff;
background-color: #606266;
border-color: #fff;
}
.image-viewer__next {
right: 40px;
text-indent: 2px;
}
.image-viewer__prev {
left: 40px;
}
.sy-image-viewer__canvas {
width: 100%;
height: 100%;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
}
.sy-image-viewer__actions {
left: 50%;
bottom: 30px;
transform: translateX(-50%);
width: 282px;
height: 44px;
padding: 0 23px;
background-color: #606266;
border-color: #fff;
border-radius: 22px;
}
.sy-image-viewer__actions__inner {
width: 100%;
height: 100%;
text-align: justify;
cursor: default;
font-size: 23px;
color: #fff;
display: flex;
align-items: center;
justify-content: space-around;
}
.header {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 72px;
background-color: #000;
line-height: 72px;
}
.file-title {
color: #fff;
text-align: center;
font-size:20px;
padding: 0 100px;
}
.not-support__box {
width: 100%;
height: 100%;
}
.not-support__wapper{
// width: 200px;
background-color: #000;
color: #fff;
padding: 50px;
text-align: center;
border: 1px solid #000;
border-radius: 4px;
.link-box {
width: 100px;
height: 100px;
display: inline-block;
border: 2px solid #fff;
border-radius: 10px;
.el-icon-link {
font-size: 100px;
}
}
.text {
font-size: 16px;
line-height: 24px;
}
}
}
</style>