218 lines
6.2 KiB
Vue
218 lines
6.2 KiB
Vue
<script setup lang="ts">
|
||
import type { FormKitFrameworkContext } from '@formkit/core';
|
||
import FileUpload, { type FileUploadUploaderEvent } from 'primevue/fileupload';
|
||
import { computed, onMounted, useTemplateRef } from 'vue';
|
||
import FileUploadItem from './file-upload-item.vue';
|
||
import type { CustomRequest, FileExt, FileUploadInst, PropFilesToValue, PropValueToFiles } from './types';
|
||
|
||
const props = defineProps<{
|
||
context: FormKitFrameworkContext & {
|
||
fileLimit?: number;
|
||
maxFileSize?: number;
|
||
customRequest?: CustomRequest;
|
||
valueToFiles?: PropValueToFiles;
|
||
filesToValue?: PropFilesToValue;
|
||
autoUpload?: boolean;
|
||
};
|
||
}>();
|
||
const formkitContext = props.context;
|
||
|
||
const customRequest = props.context.customRequest;
|
||
|
||
const fileUploadRef = useTemplateRef<FileUploadInst>('fileUploadRef');
|
||
|
||
const cmpt_disabled = computed(() => {
|
||
if (formkitContext.disabled === true) {
|
||
return true;
|
||
}
|
||
if (fileUploadRef.value) {
|
||
// 有上传失败的文件
|
||
if (fileUploadRef.value.uploadedFiles.some((f) => f.status === 'failed')) {
|
||
return true;
|
||
}
|
||
|
||
// 已上传文件数量超过限制(这里是大于*等于*)
|
||
if (fileUploadRef.value.uploadedFileCount >= (formkitContext.fileLimit ?? Infinity)) {
|
||
return true;
|
||
}
|
||
|
||
// 已上传和待上传文件数量超过限制(这里是*大于*)
|
||
const uploaded_and_pending_count = fileUploadRef.value.uploadedFileCount + fileUploadRef.value.files.length;
|
||
if (uploaded_and_pending_count > (formkitContext.fileLimit ?? Infinity)) {
|
||
return true;
|
||
}
|
||
}
|
||
return false;
|
||
});
|
||
|
||
const cmpt_showUploadButton = computed(() => {
|
||
if (fileUploadRef.value?.files?.length) {
|
||
return true;
|
||
}
|
||
return false;
|
||
});
|
||
|
||
const changeModelValue = () => {
|
||
const uploadedFiles = fileUploadRef.value!.uploadedFiles.filter((f) => f.status === 'uploaded');
|
||
if (!formkitContext.filesToValue) {
|
||
console.warn('[FileUpload] filesToValue is not defined');
|
||
return;
|
||
}
|
||
if (uploadedFiles.length === 0) {
|
||
formkitContext.node.input(null);
|
||
} else {
|
||
formkitContext.node.input(formkitContext.filesToValue(uploadedFiles));
|
||
}
|
||
};
|
||
const changeUploadedFiles = () => {
|
||
if (!formkitContext.valueToFiles) {
|
||
console.warn('[FileUpload] valueToFiles is not defined');
|
||
return;
|
||
}
|
||
try {
|
||
const files = formkitContext.valueToFiles(formkitContext._value);
|
||
if (files === null) return;
|
||
|
||
fileUploadRef.value!.uploadedFiles = files.map((f) => ({
|
||
name: f.name,
|
||
url: f.url,
|
||
status: f.status || 'uploaded',
|
||
progress: f.progress || 100,
|
||
}));
|
||
fileUploadRef.value!.uploadedFileCount = files.length;
|
||
} catch (error) {
|
||
console.warn('[FileUpload] valueToFiles error:', error, 'value:', formkitContext._value);
|
||
}
|
||
};
|
||
|
||
onMounted(() => {
|
||
changeUploadedFiles();
|
||
});
|
||
|
||
const onUploader = (event: FileUploadUploaderEvent) => {
|
||
if (!customRequest) {
|
||
console.warn('[FileUpload] customRequest is not defined');
|
||
return;
|
||
}
|
||
|
||
const files = event.files as FileExt[];
|
||
for (const file of files) {
|
||
fileUploadRef.value!.uploadedFiles.push({
|
||
rawFile: file,
|
||
name: file.name,
|
||
url: '',
|
||
status: 'uploading',
|
||
progress: 0,
|
||
});
|
||
const fileItem = fileUploadRef.value!.uploadedFiles[fileUploadRef.value!.uploadedFiles.length - 1];
|
||
customRequest({
|
||
file,
|
||
onProgress: (percent) => {
|
||
fileItem.progress = percent;
|
||
},
|
||
})
|
||
.then((result) => {
|
||
fileItem.status = 'uploaded';
|
||
fileItem.url = result.url;
|
||
changeModelValue();
|
||
})
|
||
.catch(() => {
|
||
fileItem.status = 'failed';
|
||
});
|
||
}
|
||
};
|
||
|
||
// fileUploadRef.value?.uploadedFiles
|
||
</script>
|
||
|
||
<template>
|
||
<FileUpload
|
||
ref="fileUploadRef"
|
||
:auto="formkitContext.autoUpload"
|
||
:disabled="cmpt_disabled"
|
||
:showUploadButton="cmpt_showUploadButton"
|
||
:showCancelButton="false"
|
||
:customUpload="true"
|
||
mode="advanced"
|
||
:multiple="true"
|
||
accept="image/*"
|
||
:maxFileSize="formkitContext.maxFileSize"
|
||
invalidFileSizeMessage="文件 {0} 大小超过限制 {1}"
|
||
:fileLimit="formkitContext.fileLimit"
|
||
invalidFileLimitMessage="最多只能上传 {0} 个文件,请移除多余文件后点击上传"
|
||
@uploader="onUploader"
|
||
:chooseButtonProps="{ size: 'small' }"
|
||
:uploadButtonProps="{ size: 'small', severity: 'secondary' }"
|
||
:cancelButtonProps="{ size: 'small', severity: 'secondary' }"
|
||
@remove="changeModelValue()"
|
||
@removeUploadedFile="changeModelValue()"
|
||
>
|
||
<template #empty>
|
||
<Message
|
||
size="small"
|
||
severity="secondary"
|
||
>未上传附件</Message
|
||
>
|
||
</template>
|
||
|
||
<template #content="{ messages, removeFileCallback, removeUploadedFileCallback }">
|
||
<Message
|
||
size="small"
|
||
v-for="msg of messages"
|
||
closable
|
||
@close="fileUploadRef!.messages = []"
|
||
:key="msg"
|
||
severity="error"
|
||
>{{ msg }}</Message
|
||
>
|
||
|
||
<!-- 已上传列表(上传中、上传成功、上传失败) -->
|
||
<template
|
||
v-for="(file, index) of fileUploadRef?.uploadedFiles"
|
||
:key="file.name + file.url"
|
||
>
|
||
<FileUploadItem
|
||
:disabled="formkitContext.disabled === true"
|
||
:url="file.url || file.rawFile?.objectURL || ''"
|
||
:filename="file.name || file.url || '未知文件'"
|
||
@remove="
|
||
() => {
|
||
fileUploadRef!.uploadedFileCount--;
|
||
removeUploadedFileCallback(index);
|
||
}
|
||
"
|
||
:status="file.status"
|
||
:progress="file.progress"
|
||
/>
|
||
</template>
|
||
|
||
<!-- 待上传列表 -->
|
||
<template
|
||
v-for="(file, index) of fileUploadRef?.files"
|
||
:key="file.name + file.type + file.size"
|
||
>
|
||
<FileUploadItem
|
||
:disabled="formkitContext.disabled === true"
|
||
:url="file.objectURL"
|
||
:filename="file.name"
|
||
@remove="removeFileCallback(index)"
|
||
status="pending"
|
||
/>
|
||
</template>
|
||
</template>
|
||
</FileUpload>
|
||
</template>
|
||
|
||
<style>
|
||
.p-floatlabel:has(.p-fileupload) label {
|
||
top: var(--p-floatlabel-over-active-top);
|
||
transform: translateY(0);
|
||
font-size: var(--p-floatlabel-active-font-size);
|
||
font-weight: var(--p-floatlabel-label-active-font-weight);
|
||
}
|
||
|
||
.p-fileupload-content .p-progressbar {
|
||
--p-fileupload-progressbar-height: 1rem;
|
||
}
|
||
</style>
|