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.
431 lines
12 KiB
431 lines
12 KiB
<template> |
|
<div class="flex gap-12 wrap file-container"> |
|
<div |
|
v-for="(item, index) in fileList" |
|
:key="index" |
|
class="item pointer" |
|
> |
|
<template v-if="item.type && item.type.indexOf('image') > -1"> |
|
<div |
|
class="img-box" |
|
:style="{ |
|
backgroundImage: `url(${VITE_API_URL}/api/file/stream/${item.filepath})`, |
|
}" |
|
@click="filePreview(item)" |
|
></div> |
|
<a |
|
class="remove-btn" |
|
@click="remove(index)" |
|
v-if="removeEnable" |
|
> |
|
<icon name="el-icon-CircleCloseFilled" :size="20" /> |
|
</a> |
|
</template> |
|
<div |
|
class="item flex end v-center column text-center" |
|
:title="item.orgiinFilename" |
|
@click="filePreview(item)" |
|
v-else |
|
> |
|
<icon :name="getIconName(item.type)" :size="40" /> |
|
<span class="filename">{{ item.orgiinFilename }}</span> |
|
<a |
|
class="remove-btn" |
|
@click="remove(index)" |
|
v-if="removeEnable" |
|
> |
|
<icon name="el-icon-CircleCloseFilled" :size="20" /> |
|
</a> |
|
</div> |
|
</div> |
|
</div> |
|
<div class="file-preview-wrapper flex" v-if="preview"> |
|
<div class="file-list"> |
|
<section |
|
v-for="(item, index) in fileList" |
|
:key="index" |
|
class="flex gap v-center pointer" |
|
:active="fileList.indexOf(activeFile) === index" |
|
@click="filePreview(item)" |
|
> |
|
<icon :name="getIconName(item.type)" :size="24" /> |
|
<span>{{ item.orgiinFilename }}</span> |
|
</section> |
|
</div> |
|
<div |
|
class="file-content flex center v-center" |
|
@click="preview = false" |
|
> |
|
<div |
|
class="img-container flex center" |
|
v-if="activeFile.type.indexOf('image') > -1" |
|
> |
|
<img |
|
:src="`${VITE_API_URL}/api/file/stream/${activeFile.filepath}`" |
|
ref="imgRef" |
|
@click.stop |
|
/> |
|
<button |
|
class="rotate-left-btn pointer" |
|
@click.stop="rotateLeft" |
|
> |
|
<icon name="el-icon-Back" :size="28" /> |
|
</button> |
|
<button |
|
class="rotate-right-btn pointer" |
|
@click.stop="rotateRight" |
|
> |
|
<icon name="el-icon-Right" :size="28" /> |
|
</button> |
|
</div> |
|
<template v-else-if="activeFile.type.indexOf('audio') > -1"> |
|
<audio |
|
controls |
|
:src="`${VITE_API_URL}/api/file/stream/${activeFile.filepath}`" |
|
style="width: 50vw" |
|
@click.stop |
|
></audio> |
|
</template> |
|
<template v-else-if="activeFile.type.indexOf('word') > -1"> |
|
<vue-office-docx |
|
:src="getDocFilepath()" |
|
style="height: 100vh; width: 900px" |
|
@error="fileRrror = true" |
|
v-if="!fileRrror" |
|
@click.stop |
|
/> |
|
<div v-else class="error flex column text-center"> |
|
<img src="/imgs/error.png" alt="" /> |
|
<span class="mb-20" |
|
>文件预览解析错误,如有需要请下载到本地预览</span |
|
> |
|
</div> |
|
</template> |
|
<template |
|
v-else-if=" |
|
activeFile.type.indexOf('excel') > -1 || |
|
activeFile.type.indexOf('spreadsheetml.sheet') > -1 |
|
" |
|
> |
|
<vue-office-excel |
|
:src="`${VITE_API_URL}/api/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"> |
|
<img src="/imgs/error.png" alt="" /> |
|
<span class="mb-20" |
|
>文件预览解析错误,如有需要请下载到本地预览</span |
|
> |
|
</div> |
|
</template> |
|
<template v-else-if="activeFile.type === 'application/pdf'"> |
|
<iframe |
|
:src="`${VITE_API_URL}/api/file/stream/${activeFile.filepath}`" |
|
style="height: 100vh; width: 900px" |
|
></iframe> |
|
</template> |
|
<div class="file-number" @click.stop> |
|
<span |
|
>{{ fileList.indexOf(activeFile) + 1 }} / |
|
{{ fileList.length }}</span |
|
> |
|
</div> |
|
<button class="left-btn pointer" @click.stop="prev()"> |
|
<icon name="el-icon-ArrowLeftBold" :size="28" /> |
|
</button> |
|
<button class="right-btn pointer" @click.stop="next()"> |
|
<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" |
|
size="large" |
|
> |
|
<template #icon> |
|
<icon name="el-icon-Download" :size="20" /> |
|
</template> |
|
下载文件 |
|
</el-button> |
|
</div> |
|
</template> |
|
<script setup> |
|
import feedback from "@/utils/feedback"; |
|
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 { VITE_API_URL } = process.env; |
|
const router = useRouter(); |
|
|
|
const props = defineProps({ |
|
files: { |
|
type: Array, |
|
default: () => [], |
|
}, |
|
removeEnable: { |
|
type: Boolean, |
|
default: false, |
|
}, |
|
}); |
|
const emit = defineEmits(["update:files"]); |
|
|
|
const fileList = ref(props.files); |
|
|
|
watch( |
|
() => props.files, |
|
(files) => { |
|
fileList.value = files; |
|
} |
|
); |
|
|
|
const preview = ref(false); |
|
const activeFile = ref({}); |
|
const fileRrror = ref(false); |
|
const imgRef = ref(); |
|
let rotate = 0; |
|
|
|
function prev() { |
|
const index = fileList.value.indexOf(activeFile.value); |
|
if (index === 0) { |
|
filePreview(fileList.value[fileList.value.length - 1]) |
|
} else { |
|
filePreview(fileList.value[index - 1]) |
|
} |
|
} |
|
function next() { |
|
const index = fileList.value.indexOf(activeFile.value); |
|
if (index === fileList.value.length - 1) { |
|
filePreview(fileList.value[0]) |
|
} else { |
|
filePreview(fileList.value[index + 1]) |
|
} |
|
} |
|
function filePreview(file) { |
|
preview.value = true; |
|
fileRrror.value = false; |
|
activeFile.value = file; |
|
rotate = 0 |
|
imgRef.value.style.transform = `rotate(0deg)`; |
|
} |
|
|
|
function rotateLeft() { |
|
imgRef.value.style.transform = `rotate(${(rotate += 90)}deg)`; |
|
} |
|
|
|
function rotateRight() { |
|
imgRef.value.style.transform = `rotate(${(rotate -= 90)}deg)`; |
|
} |
|
|
|
function getIconName(filetype) { |
|
if (!filetype) { |
|
return "el-icon-document"; |
|
} |
|
if (filetype.indexOf("image") > -1) { |
|
return "el-icon-Picture"; |
|
} |
|
if (filetype === "application/pdf") { |
|
return "local-icon-pdf"; |
|
} |
|
if (filetype.indexOf("audio") > -1) { |
|
return "local-icon-mp3"; |
|
} |
|
if (filetype.indexOf("word") > -1) { |
|
return "local-icon-doc"; |
|
} |
|
if ( |
|
filetype.indexOf("excel") > -1 || |
|
filetype.indexOf("spreadsheetml.sheet") > -1 |
|
) { |
|
return "local-icon-xls"; |
|
} |
|
return "el-icon-document"; |
|
} |
|
|
|
function remove(index) { |
|
fileList.value.splice(index, 1); |
|
emit("update:files", fileList.value); |
|
} |
|
|
|
function download() { |
|
window.open(`${VITE_API_URL}/api/file/stream/${activeFile.value.filepath}`); |
|
} |
|
|
|
function getDocFilepath() { |
|
if ( |
|
activeFile.value.type === "application/msword" && |
|
activeFile.value.docxFilepath |
|
) { |
|
return `${VITE_API_URL}/api/file/stream/${activeFile.value.docxFilepath}`; |
|
} |
|
return `${VITE_API_URL}/api/file/stream/${activeFile.value.filepath}`; |
|
} |
|
</script> |
|
<style lang="scss" scoped> |
|
.file-container { |
|
min-height: 80px; |
|
.item { |
|
width: 80px; |
|
height: 80px; |
|
margin-bottom: 12px; |
|
border-radius: 2px; |
|
color: var(--primary-color); |
|
position: relative; |
|
&:hover { |
|
background-color: #ededed; |
|
span.filename { |
|
font-weight: 700; |
|
} |
|
} |
|
span.filename { |
|
line-height: 1.2; |
|
font-size: 12px; |
|
width: 100%; |
|
white-space: nowrap; |
|
text-overflow: ellipsis; |
|
margin-top: 10px; |
|
overflow: hidden; |
|
} |
|
.img-box { |
|
width: 80px; |
|
height: 80px; |
|
background-size: cover; |
|
background-position: center; |
|
border-radius: 2px; |
|
&:hover { |
|
outline: 2px solid #ff9800; |
|
} |
|
} |
|
.remove-btn { |
|
position: absolute; |
|
top: -10px; |
|
right: -10px; |
|
display: block; |
|
border-radius: 50%; |
|
height: 20px; |
|
background-color: #fff; |
|
&:hover { |
|
color: red; |
|
cursor: pointer; |
|
} |
|
} |
|
} |
|
} |
|
.file-preview-wrapper { |
|
position: fixed; |
|
left: 0; |
|
top: 0; |
|
right: 0; |
|
bottom: 0; |
|
background-color: rgba(0, 0, 0, 0.7); |
|
z-index: 999; |
|
.file-list { |
|
width: 14vw; |
|
height: 100vh; |
|
padding: 16px 8px; |
|
background-color: #fff; |
|
section { |
|
padding: 8px 16px; |
|
border: 2px solid transparent; |
|
&: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 { |
|
height: 100vh; |
|
img { |
|
max-height: 100%; |
|
display: block; |
|
} |
|
} |
|
.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: 108px; |
|
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: 16px; |
|
right: 18px; |
|
} |
|
.file-number { |
|
position: absolute; |
|
top: 16px; |
|
left: 18px; |
|
color: #fff; |
|
} |
|
} |
|
</style> |