<script lang="ts" setup>
|
import type { fileItem } from './props';
|
|
import { computed, ref, unref, watch } from 'vue';
|
|
import { useGlobSetting } from '@jnpf/hooks';
|
import { createImgPreview } from '@jnpf/ui';
|
import { downloadByUrl, toFileSize } from '@jnpf/utils';
|
|
import { $t } from '@vben/locales';
|
import { useUserStore } from '@vben/stores';
|
|
import { globalShareState } from '@vben-core/shared/global-state';
|
|
import { CloseOutlined, DownloadOutlined, EyeOutlined, PaperClipOutlined } from '@ant-design/icons-vue';
|
import { Form, message } from 'ant-design-vue';
|
|
import Preview from './Preview.vue';
|
import { uploadFileProps } from './props';
|
import FileUploader from './SimpleUploader/FileUploader.vue';
|
|
defineOptions({ inheritAttrs: false, name: 'JnpfUploadFile' });
|
const props = defineProps(uploadFileProps);
|
const emit = defineEmits(['update:value', 'change']);
|
|
const { getDownloadUrl, getPackDownloadUrl } = globalShareState.getApi();
|
const userStore = useUserStore();
|
const globSetting = useGlobSetting();
|
const fileList = ref<fileItem[]>([]);
|
const apiUrl = ref(globSetting.apiURL);
|
const videoTypeList = new Set(['avi', 'avi', 'flv', 'flv', 'mkv', 'mov', 'mp3', 'mp4', 'mpeg', 'mpg', 'mpg', 'ram', 'rm', 'rm', 'rmvb', 'swf', 'wma', 'wmv']);
|
const imgTypeList = new Set(['bmp', 'gif', 'jpeg', 'jpg', 'png']);
|
const zipTypeList = new Set(['7z', 'arj', 'rar', 'z', 'zip']);
|
const filePreviewRef = ref<any>(null);
|
const fileUploaderRef = ref<any>(null);
|
const formItemContext = Form.useInjectFormItemContext();
|
|
const getBindValue = computed(() => ({ ...props, value: fileList.value }));
|
|
watch(
|
() => unref(props.value),
|
(val) => {
|
fileList.value = val && Array.isArray(val) ? val : [];
|
},
|
{
|
deep: true,
|
immediate: true,
|
},
|
);
|
|
defineExpose({ uploadFile });
|
|
function handleSimplePreview(file: fileItem) {
|
if (!props.simple) return;
|
handlePreview(file);
|
}
|
function handlePreview(file: fileItem) {
|
if (videoTypeList.has(file.fileExtension || '')) {
|
message.error($t('component.upload.videoNoPreview'));
|
return;
|
}
|
if (zipTypeList.has(file.fileExtension || '')) {
|
message.error($t('component.upload.zipNoPreview'));
|
return;
|
}
|
// 图片预览
|
if (imgTypeList.has(file.fileExtension || '')) {
|
const imageList = [getImgUrl(file.url)];
|
createImgPreview({ imageList });
|
return;
|
}
|
// 文件预览
|
filePreviewRef.value?.init(file);
|
}
|
function getImgUrl(url) {
|
const userInfo: any = userStore.getUserInfo || {};
|
const securityKey = userInfo?.securityKey || '';
|
if (!securityKey) return apiUrl.value + url;
|
const realUrl = `${apiUrl.value + url + (url.includes('?') ? '&' : '?')}s=${securityKey}`;
|
return realUrl;
|
}
|
function handleDownload(file: fileItem) {
|
if (!file.fileId) return;
|
getDownloadUrl(props.type, file.fileId).then((res) => {
|
downloadByUrl({ fileName: file.name, url: res.data.url });
|
});
|
}
|
// 批量下载
|
function handleDownloadAll() {
|
const data: any[] = [];
|
for (let i = 0; i < unref(fileList).length; i++) {
|
const item: any = unref(fileList)[i];
|
data.push({ fileId: item.fileId, fileName: item.name });
|
}
|
getPackDownloadUrl(props.type, data).then((res) => {
|
if (!res.data && !res.data.downloadVo) return;
|
downloadByUrl({ fileName: res.data.downloadName, url: res.data.downloadVo.url });
|
});
|
}
|
function handleRemove(index: number) {
|
fileList.value.splice(index, 1);
|
emit('update:value', unref(fileList));
|
emit('change', unref(fileList));
|
formItemContext.onFieldChange();
|
}
|
function uploadFile() {
|
const isTopLimit = props.limit ? fileList.value.length >= props.limit : false;
|
if (isTopLimit) {
|
message.error($t('component.upload.fileMaxNumber', [props.limit]));
|
return false;
|
}
|
fileUploaderRef.value?.openUploader();
|
}
|
function handleFileSuccess(data) {
|
const isTopLimit = props.limit ? fileList.value.length >= props.limit : false;
|
if (isTopLimit) {
|
message.error($t('component.upload.fileMaxNumber', [props.limit]));
|
return false;
|
}
|
fileList.value.push(data);
|
emit('update:value', unref(fileList));
|
emit('change', unref(fileList));
|
formItemContext.onFieldChange();
|
}
|
</script>
|
|
<template>
|
<div :class="$attrs.class" class="upload-file-container">
|
<div class="flex justify-between">
|
<a-button :disabled="disabled" pre-icon="icon-ym icon-ym-btn-upload" @click="uploadFile" v-if="!detailed">{{ buttonText }}</a-button>
|
<p v-if="fileList.length > 0 && showAllDownload" class="link-text" @click="handleDownloadAll">
|
<DownloadOutlined />{{ $t('component.upload.downloadAll') }}
|
</p>
|
</div>
|
<div v-if="tipText && !detailed" class="upload-file__tip">{{ tipText }}</div>
|
<FileUploader ref="fileUploaderRef" v-bind="getBindValue" @file-success="handleFileSuccess" />
|
<div v-if="showUploadList" :class="{ 'upload-file-list__simple': simple }" class="upload-file-list">
|
<div v-for="(file, index) in fileList" :key="file.fileId" :class="{ 'upload-file-list__item_detail': detailed }" class="upload-file-list__item">
|
<a
|
:title="file.name + (file.fileSize && !simple ? toFileSize(file.fileSize) : '')"
|
class="upload-file-list__item-name"
|
@click="handleSimplePreview(file)">
|
<PaperClipOutlined v-if="showIcon" />
|
{{ file.name }}{{ file.fileSize && !simple ? toFileSize(file.fileSize) : '' }}
|
</a>
|
<span class="upload-file-list__item-actions">
|
<EyeOutlined v-if="showView" :title="$t('component.upload.view')" @click="handlePreview(file)" />
|
<DownloadOutlined v-if="showDownload" :title="$t('component.upload.download')" @click="handleDownload(file)" />
|
<CloseOutlined v-show="!disabled && !detailed" :title="$t('component.upload.del')" @click="handleRemove(index)" />
|
</span>
|
</div>
|
</div>
|
<Preview ref="filePreviewRef" :show-download="simple" :type="type" />
|
</div>
|
</template>
|
<style lang="scss" scoped>
|
.upload-file-container {
|
.link-text {
|
line-height: 32px;
|
|
.anticon {
|
margin-right: 2px;
|
}
|
}
|
|
.upload-file__tip {
|
margin-top: 5px;
|
font-size: 12px;
|
line-height: 1.2;
|
color: var(--text-color-secondary);
|
word-break: break-all;
|
}
|
|
.upload-file-list {
|
&.upload-file-list__simple {
|
.upload-file-list__item {
|
color: var(--primary-color);
|
|
&:first-child {
|
margin-top: 0 !important;
|
}
|
|
&:hover {
|
background-color: unset !important;
|
}
|
|
.upload-file-list__item-name {
|
padding-left: 0;
|
margin-right: 0;
|
|
.anticon {
|
display: none;
|
}
|
}
|
|
.anticon-eye,
|
.anticon-download,
|
.anticon-close {
|
display: none;
|
}
|
}
|
}
|
|
.upload-file-list__item {
|
position: relative;
|
box-sizing: border-box;
|
width: 100%;
|
margin-top: 5px;
|
font-size: 14px;
|
line-height: 26px;
|
color: var(--text-color-label);
|
border-radius: 4px;
|
|
a {
|
color: inherit;
|
}
|
|
&:first-child {
|
margin-top: 10px;
|
}
|
|
&:hover {
|
background-color: var(--selected-hover-bg);
|
|
.upload-file-list__item-name {
|
color: var(--primary-color);
|
}
|
}
|
|
&.upload-file-list__item_detail:first-child {
|
margin-top: 4px !important;
|
}
|
|
.upload-file-list__item-name {
|
display: block;
|
padding-left: 4px;
|
margin-right: 70px;
|
overflow: hidden;
|
text-overflow: ellipsis;
|
white-space: nowrap;
|
|
.anticon {
|
height: 100%;
|
margin-right: 5px;
|
color: #909399;
|
}
|
}
|
|
.anticon-eye {
|
position: absolute;
|
top: 6px;
|
right: 45px;
|
display: inline-block;
|
color: var(--text-color-label);
|
cursor: pointer;
|
opacity: 0.75;
|
}
|
|
.anticon-download {
|
position: absolute;
|
top: 6px;
|
right: 25px;
|
display: inline-block;
|
color: var(--text-color-label);
|
cursor: pointer;
|
opacity: 0.75;
|
}
|
|
.anticon-close {
|
position: absolute;
|
top: 6px;
|
right: 5px;
|
display: inline-block;
|
color: var(--text-color-label);
|
cursor: pointer;
|
opacity: 0.75;
|
}
|
}
|
}
|
|
.list-enter-active,
|
.list-leave-active {
|
transition: all 1s ease;
|
}
|
|
.list-enter-from,
|
.list-leave-to {
|
opacity: 0;
|
transform: translateY(-30px);
|
}
|
}
|
</style>
|