mirror of
https://gitee.com/lijingbo-2021/open-anylink-web.git
synced 2025-12-30 11:02:25 +00:00
242 lines
6.1 KiB
Vue
242 lines
6.1 KiB
Vue
<script setup>
|
|
import { ref, onMounted, computed } from 'vue'
|
|
import { formatFileSize } from '@/js/utils/common'
|
|
import DocumentIcon from '@/assets/svg/document.svg'
|
|
import ArchiveIcon from '@/assets/svg/archive.svg'
|
|
import FileTemplateIcon from '@/assets/svg/filetemplate.svg'
|
|
import { CircleCheckFilled, WarningFilled } from '@element-plus/icons-vue'
|
|
import { ElMessage } from 'element-plus'
|
|
|
|
const props = defineProps(['url', 'fileName', 'contentType', 'size'])
|
|
const emits = defineEmits(['load'])
|
|
|
|
const iconMap = {
|
|
'text/csv': 'CSV',
|
|
'application/msword': 'DOC',
|
|
'application/vnd.openxmlformats-officedocument.wordprocessingml.document': 'DOCX',
|
|
'text/html': 'HTML',
|
|
'application/pdf': 'PDF',
|
|
'application/vnd.ms-powerpoint': 'PPT',
|
|
'application/vnd.openxmlformats-officedocument.presentationml.presentation': 'PPTX',
|
|
'text/plain': 'TXT',
|
|
'application/vnd.ms-works': 'WPS',
|
|
'application/vnd.ms-excel': 'XLS',
|
|
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': 'XLSX',
|
|
'application/zip': ArchiveIcon,
|
|
'application/vnd.rar': ArchiveIcon,
|
|
'application/x-zip-compressed': ArchiveIcon,
|
|
'application/x-7z-compressed': ArchiveIcon,
|
|
'application/x-tar': ArchiveIcon,
|
|
'application/gzip': ArchiveIcon,
|
|
'application/x-bzip2': ArchiveIcon
|
|
}
|
|
|
|
const iconComponent = computed(() => {
|
|
return iconMap[props.contentType] || DocumentIcon
|
|
})
|
|
|
|
const formatSize = computed(() => {
|
|
return formatFileSize(props.size)
|
|
})
|
|
|
|
const isDownloading = ref(false)
|
|
const isDownloadComplete = ref(false)
|
|
const isDownloadError = ref(false)
|
|
const progress = ref(0)
|
|
|
|
onMounted(() => {
|
|
emits('load') //向父组件暴露load事件
|
|
})
|
|
|
|
const onDownload = async () => {
|
|
isDownloading.value = true
|
|
isDownloadComplete.value = false
|
|
isDownloadError.value = false
|
|
progress.value = 0
|
|
try {
|
|
const response = await fetch(props.url)
|
|
if (!response.ok) {
|
|
ElMessage.error('文件资源异常,请稍后再试。')
|
|
isDownloading.value = false
|
|
isDownloadError.value = true
|
|
return
|
|
}
|
|
const contentLength = response.headers.get('content-length')
|
|
const total = contentLength ? parseInt(contentLength, 10) : 0
|
|
let loaded = 0
|
|
|
|
const reader = response.body.getReader()
|
|
const chunks = []
|
|
// eslint-disable-next-line no-constant-condition
|
|
while (true) {
|
|
const { done, value } = await reader.read()
|
|
if (done) break
|
|
chunks.push(value)
|
|
loaded += value.length
|
|
progress.value = total > 0 ? (loaded / total) * 100 : 0
|
|
}
|
|
const blob = new Blob(chunks)
|
|
const url = URL.createObjectURL(blob)
|
|
|
|
const a = document.createElement('a')
|
|
a.href = url
|
|
a.download = props.fileName
|
|
a.click()
|
|
|
|
URL.revokeObjectURL(url)
|
|
isDownloading.value = false
|
|
isDownloadComplete.value = true
|
|
} catch (error) {
|
|
ElMessage.error('下载文件时出错,请稍后再试。')
|
|
isDownloading.value = false
|
|
isDownloadError.value = true
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div class="document-msg-wrapper">
|
|
<div v-if="typeof iconComponent === 'string'" class="file-template">
|
|
<FileTemplateIcon></FileTemplateIcon>
|
|
<span class="extension">{{ iconComponent }}</span>
|
|
</div>
|
|
<component v-else :is="iconComponent" />
|
|
<div class="main">
|
|
<span class="file-name text-ellipsis" :title="props.fileName || '未知'">
|
|
{{ props.fileName || '未知' }}
|
|
</span>
|
|
<div class="footer">
|
|
<div class="size" :title="formatSize">{{ formatSize }}</div>
|
|
<div class="download">
|
|
<span
|
|
v-if="!isDownloading && !isDownloadComplete && !isDownloadError"
|
|
@click="onDownload"
|
|
style="cursor: pointer"
|
|
>
|
|
下载
|
|
</span>
|
|
<div
|
|
v-else-if="isDownloading"
|
|
class="loading-ring"
|
|
:style="{ '--progress': progress + '%' }"
|
|
>
|
|
<div class="progress"></div>
|
|
</div>
|
|
<div
|
|
v-else-if="!isDownloading && isDownloadComplete"
|
|
class="check-success"
|
|
title="已下载"
|
|
>
|
|
<CircleCheckFilled />
|
|
</div>
|
|
<div
|
|
v-else-if="!isDownloading && isDownloadError"
|
|
class="check-fail"
|
|
title="点击重试"
|
|
@click="onDownload"
|
|
>
|
|
<WarningFilled />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style lang="scss" scoped>
|
|
.document-msg-wrapper {
|
|
padding: 4px 8px 4px 8px;
|
|
display: flex;
|
|
gap: 10px;
|
|
|
|
.file-template {
|
|
position: relative;
|
|
|
|
.extension {
|
|
width: 100%;
|
|
position: absolute;
|
|
left: 0;
|
|
top: 16px;
|
|
font-size: 16px;
|
|
line-height: 24px;
|
|
font-weight: bold;
|
|
letter-spacing: -2px;
|
|
text-align: center;
|
|
color: #fff;
|
|
user-select: none;
|
|
}
|
|
}
|
|
|
|
.svg-icon {
|
|
width: 48px;
|
|
height: 48px;
|
|
}
|
|
|
|
.main {
|
|
width: 140px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: space-around;
|
|
gap: 8px;
|
|
|
|
.file-name {
|
|
font-size: 14px;
|
|
}
|
|
|
|
.footer {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
font-size: 12px;
|
|
|
|
.download {
|
|
width: 32px;
|
|
color: blue;
|
|
display: flex;
|
|
justify-content: center;
|
|
}
|
|
|
|
.loading-ring {
|
|
position: relative;
|
|
width: 18px;
|
|
height: 18px;
|
|
border-radius: 50%;
|
|
display: inline-block;
|
|
vertical-align: middle;
|
|
|
|
.progress {
|
|
position: absolute;
|
|
top: 50%;
|
|
left: 50%;
|
|
transform: translate(-50%, -50%);
|
|
width: 12px;
|
|
height: 12px;
|
|
background-color: white;
|
|
border-radius: 50%;
|
|
}
|
|
}
|
|
|
|
.loading-ring:not(.completed) {
|
|
background: conic-gradient(#007bff 0% var(--progress), #ccc var(--progress) 100%);
|
|
}
|
|
|
|
.loading-ring.completed {
|
|
background: #007bff;
|
|
}
|
|
|
|
.check-success {
|
|
width: 18px;
|
|
height: 18px;
|
|
color: #95d475;
|
|
}
|
|
|
|
.check-fail {
|
|
width: 18px;
|
|
height: 18px;
|
|
color: red;
|
|
cursor: pointer;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
</style>
|