You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
421 lines
12 KiB
421 lines
12 KiB
<template> |
|
<div class="file-preview-wrapper flex overlay" v-if="preview"> |
|
<el-scrollbar height="100vh"> |
|
<div class="file-list"> |
|
<section |
|
v-for="(item, index) in files" |
|
:key="index" |
|
class="flex gap v-center pointer" |
|
:active="files.indexOf(activeFile) === index" |
|
@click="filePreview(item)" |
|
> |
|
<icon :name="getIconName(item.fileName)" :size="24" /> |
|
<span>{{ item.fileName }}</span> |
|
</section> |
|
</div> |
|
</el-scrollbar> |
|
<div class="file-content flex center v-center" @click="preview = false"> |
|
<div |
|
class="img-container flex center" |
|
v-if="getFileType(activeFile.fileName) === FileType.IMG" |
|
@wheel="wheel" |
|
> |
|
<img |
|
:src="`${BASE_PATH}/file/stream/${activeFile.filePath}`" |
|
ref="imgRef" |
|
@click.stop |
|
:style="{ |
|
transform: `rotate(${rotate}deg) scale(${scale}) translate(${translateX}px, ${translateY}px)`, |
|
}" |
|
@mousedown="mousedown" |
|
@mousemove="mousemove" |
|
@mouseup="mouseup" |
|
draggable="false" |
|
/> |
|
<button |
|
class="rotate-left-btn pointer" |
|
@click.stop.prevent="rotateLeft" |
|
size="small" |
|
title="左旋转" |
|
> |
|
<icon name="local-icon-rotate-left" :size="28" /> |
|
</button> |
|
<button |
|
class="rotate-right-btn pointer" |
|
@click.stop.prevent="rotateRight" |
|
size="small" |
|
title="右旋转" |
|
> |
|
<icon name="local-icon-rotate-right" :size="28" /> |
|
</button> |
|
</div> |
|
<template |
|
v-else-if="getFileType(activeFile.fileName) === FileType.PDF" |
|
> |
|
<iframe |
|
:src="`${BASE_PATH}/file/stream/${activeFile.filePath}`" |
|
style="height: 100vh; width: 900px" |
|
></iframe> |
|
</template> |
|
<template |
|
v-else-if="getFileType(activeFile.fileName) === FileType.MP3" |
|
> |
|
<audio controls style="width: 50vw"> |
|
<source |
|
:src="`${BASE_PATH}/file/stream/${activeFile.filePath}`" |
|
type="audio/mp3" |
|
/> |
|
</audio> |
|
</template> |
|
<template |
|
v-else-if="getFileType(activeFile.fileName) === FileType.MP4" |
|
> |
|
<video controls @click.stop style="max-height: 100vh"> |
|
<source |
|
:src="`${BASE_PATH}/file/stream/${activeFile.filePath}`" |
|
type="video/mp4" |
|
/> |
|
</video> |
|
</template> |
|
<template |
|
v-else-if="getFileType(activeFile.fileName) === FileType.WORD" |
|
> |
|
<vue-office-docx |
|
:src="`${BASE_PATH}/file/stream/${activeFile.filePath}`" |
|
style="height: 100vh; width: 900px" |
|
@error="fileRrror = true" |
|
v-if="!fileRrror" |
|
@click.stop |
|
/> |
|
<div v-else class="error flex column text-center"> |
|
<span style="padding: 20px" |
|
>文件预览解析错误,如有需要请下载到本地预览</span |
|
> |
|
</div> |
|
</template> |
|
<template |
|
v-else-if=" |
|
getFileType(activeFile.fileName) === FileType.EXCEL && |
|
activeFile.fileName.toLocaleLowerCase().endsWith('.xlsx') |
|
" |
|
> |
|
<vue-office-excel |
|
:src="`${BASE_PATH}/file/stream/${activeFile.filePath}`" |
|
style="height: 100vh; width: 60vw" |
|
@error="fileRrror = true" |
|
v-if="!fileRrror" |
|
@click.stop |
|
/> |
|
<div v-else class="error flex column text-center"> |
|
<span style="padding: 20px" |
|
>文件预览解析错误,如有需要请下载到本地预览</span |
|
> |
|
</div> |
|
</template> |
|
<template v-else> |
|
<div style="background: #fff"> |
|
<el-result |
|
icon="error" |
|
title="不支持预览" |
|
sub-title="该文件格式暂不支持预览,请下载预览" |
|
style="background: #fff; width: 600px; height: 400px" |
|
@click.stop |
|
> |
|
<template #extra> |
|
<el-button |
|
type="primary" |
|
text |
|
size="large" |
|
@click="download" |
|
>下载文件</el-button |
|
> |
|
</template> |
|
</el-result> |
|
</div> |
|
</template> |
|
<div class="file-number" @click.stop> |
|
<span |
|
>{{ files.indexOf(activeFile) + 1 }} / |
|
{{ files.length }}</span |
|
> |
|
</div> |
|
<button |
|
class="left-btn pointer" |
|
@click.stop.prevent="prev" |
|
v-if="files.length > 1" |
|
> |
|
<icon name="el-icon-ArrowLeftBold" :size="28" /> |
|
</button> |
|
<button |
|
class="right-btn pointer" |
|
@click.stop.prevent="next" |
|
v-if="files.length > 1" |
|
> |
|
<icon name="el-icon-ArrowRightBold" :size="28" /> |
|
</button> |
|
</div> |
|
<div class="close-btn"></div> |
|
<button class="close-btn pointer" @click="preview = false"> |
|
<icon name="el-icon-Close" :size="28" /> |
|
</button> |
|
|
|
<el-button class="download-btn" @click="download" type="primary" plain> |
|
<template #icon> |
|
<icon name="el-icon-Download" :size="20" /> |
|
</template> |
|
下载文件 |
|
</el-button> |
|
</div> |
|
</template> |
|
<script setup> |
|
import { BASE_PATH } from "@/api/request"; |
|
import { FileType } from "@/enums/fileEnums"; |
|
import { getFileType, getIconName } from "@/utils/util"; |
|
import "@vue-office/docx/lib/index.css"; |
|
import "@vue-office/excel/lib/index.css"; |
|
|
|
import VueOfficeDocx from "@vue-office/docx"; |
|
import VueOfficeExcel from "@vue-office/excel"; |
|
|
|
const props = defineProps({ |
|
files: { |
|
type: Array, |
|
default: () => [], |
|
}, |
|
show: { |
|
type: Boolean, |
|
default: false, |
|
}, |
|
}); |
|
const emit = defineEmits(["update:show"]); |
|
|
|
const preview = ref(false); |
|
const activeFile = ref({}); |
|
|
|
watch( |
|
() => props.show, |
|
(val) => { |
|
preview.value = val; |
|
nextTick(() => { |
|
if ( |
|
val && |
|
Object.keys(activeFile.value).length === 0 && |
|
props.files.length > 0 |
|
) { |
|
activeFile.value = props.files[0]; |
|
rotate.value = 0; |
|
scale.value = 1; |
|
translateX.value = 0; |
|
translateY.value = 0; |
|
moveFlag = false; |
|
} |
|
}); |
|
} |
|
); |
|
|
|
watch(preview, (val) => { |
|
emit("update:show", val); |
|
}); |
|
|
|
const rotate = ref(0); |
|
const scale = ref(0); |
|
const translateX = ref(0); |
|
const translateY = ref(0); |
|
let moveFlag = false; |
|
let initialX = 0; |
|
let initialY = 0; |
|
const fileRrror = ref(false); |
|
|
|
function filePreview(file) { |
|
preview.value = true; |
|
activeFile.value = file; |
|
rotate.value = 0; |
|
scale.value = 1; |
|
translateX.value = 0; |
|
translateY.value = 0; |
|
moveFlag = false; |
|
} |
|
|
|
function download() { |
|
window.open(`${BASE_PATH}/file/stream/${activeFile.value.filePath}`); |
|
} |
|
|
|
function prev() { |
|
const index = props.files.indexOf(activeFile.value); |
|
if (index === 0) { |
|
filePreview(props.files[props.files.length - 1]); |
|
} else { |
|
filePreview(props.files[index - 1]); |
|
} |
|
} |
|
function next() { |
|
const index = props.files.indexOf(activeFile.value); |
|
if (index === props.files.length - 1) { |
|
filePreview(props.files[0]); |
|
} else { |
|
filePreview(props.files[index + 1]); |
|
} |
|
} |
|
|
|
function wheel(event) { |
|
if (event.deltaY > 0 && scale.value > 0.5) { |
|
scale.value -= 0.1; |
|
} |
|
if (event.deltaY < 0) { |
|
scale.value += 0.1; |
|
} |
|
} |
|
|
|
function mousedown() { |
|
moveFlag = true; |
|
initialX = event.clientX; |
|
initialY = event.clientY; |
|
} |
|
function mousemove(event) { |
|
if (!moveFlag) { |
|
return; |
|
} |
|
if (rotate.value % 360 === 0) { |
|
translateX.value += event.clientX - initialX; |
|
translateY.value += event.clientY - initialY; |
|
} |
|
if (rotate.value === 90) { |
|
translateY.value -= event.clientX - initialX; |
|
translateX.value += event.clientY - initialY; |
|
} |
|
if (rotate.value === 180) { |
|
translateX.value -= event.clientX - initialX; |
|
translateY.value -= event.clientY - initialY; |
|
} |
|
if (rotate.value === 270) { |
|
translateY.value += event.clientX - initialX; |
|
translateX.value -= event.clientY - initialY; |
|
} |
|
initialX = event.clientX; |
|
initialY = event.clientY; |
|
} |
|
|
|
function mouseup(event) { |
|
moveFlag = false; |
|
} |
|
|
|
function rotateLeft() { |
|
if (rotate.value === 360) { |
|
rotate.value = 0; |
|
} else { |
|
rotate.value += 90; |
|
} |
|
} |
|
|
|
function rotateRight() { |
|
if (rotate.value === 0) { |
|
rotate.value = 270; |
|
} else { |
|
rotate.value -= 90; |
|
} |
|
} |
|
</script> |
|
<style lang="scss" scoped> |
|
.file-preview-wrapper { |
|
.file-list { |
|
width: 15vw; |
|
height: 100vh; |
|
padding: 16px 8px; |
|
background-color: #fff; |
|
box-sizing: border-box; |
|
section { |
|
padding: 8px 16px; |
|
border: 2px solid transparent; |
|
background-color: #fff; |
|
&:hover { |
|
color: var(--primary-color); |
|
font-weight: 700; |
|
} |
|
&[active="true"] { |
|
border-color: var(--primary-color); |
|
} |
|
span { |
|
width: calc(100% - 32px); |
|
overflow: hidden; |
|
white-space: nowrap; |
|
text-overflow: ellipsis; |
|
} |
|
} |
|
} |
|
.file-content { |
|
width: 86vw; |
|
position: relative; |
|
|
|
.img-container { |
|
img { |
|
max-height: 100vh; |
|
&:hover { |
|
cursor: pointer; |
|
} |
|
} |
|
} |
|
.error { |
|
background-color: #fff; |
|
img { |
|
width: 500px; |
|
} |
|
} |
|
} |
|
.close-btn { |
|
position: absolute; |
|
top: 12px; |
|
right: 8px; |
|
background-color: transparent; |
|
border: none; |
|
color: #fff; |
|
&:hover { |
|
color: red; |
|
} |
|
} |
|
.rotate-left-btn { |
|
position: absolute; |
|
top: 12px; |
|
right: 118px; |
|
background-color: transparent; |
|
border: none; |
|
color: #fff; |
|
} |
|
.rotate-right-btn { |
|
position: absolute; |
|
top: 12px; |
|
right: 68px; |
|
background-color: transparent; |
|
border: none; |
|
color: #fff; |
|
} |
|
.left-btn { |
|
position: absolute; |
|
top: 50%; |
|
left: 0; |
|
transform: translateY(-50%); |
|
background-color: transparent; |
|
border: none; |
|
color: #fff; |
|
} |
|
.right-btn { |
|
position: absolute; |
|
top: 50%; |
|
right: 0; |
|
transform: translateY(-50%); |
|
background-color: transparent; |
|
border: none; |
|
color: #fff; |
|
} |
|
.download-btn { |
|
position: absolute; |
|
bottom: 20px; |
|
right: 20px; |
|
} |
|
.file-number { |
|
position: absolute; |
|
top: 16px; |
|
left: 18px; |
|
color: #fff; |
|
} |
|
} |
|
</style> |