<script lang="ts" setup>
import { IconMore, IconSUpload, IconXSolid } from '@computing/opendesign-icons';
import { PropType, reactive, Ref, ref, toRefs } from 'vue';
import { UploadFile } from 'element-plus/es/components/upload/src/upload';
import { IUploadConfig } from '@/model/base-interface';
import $http from '@/utils/http-service';
const props = defineProps({
data: {
type: Object as PropType<IUploadConfig>,
required: true,
default: () => {
return {};
}
}
});
const { data } = toRefs(props);
const uploadConfig = reactive(data.value);
const style = { width: data.value.width ? data.value.width + 'px' : '100%' };
const fileItem: Ref<UploadFile | null> = ref(null);
const fileName = ref('');
const upload = ref();
const container = ref<HTMLElement>();
const isError = ref(false);
const errorMsg = ref('');
const isDragOver = ref(false);
const addFile = (file?: File | null) => {
if (!file || uploadConfig.disabled) {
return;
}
upload.value.clearFiles();
upload.value.handleStart(file);
};
const handleSuccess = (response: any, file: UploadFile, list: UploadFile[]) => {
if (uploadConfig.onSuccess) {
uploadConfig.onSuccess(response, file, list);
}
};
const handleError = (error: any, file: UploadFile, list: UploadFile[]) => {
isError.value = true;
if (uploadConfig.onError) {
uploadConfig.onError(error, file, list);
}
};
const handleExceed = (files: UploadFile[]) => {
upload.value.clearFiles();
upload.value.handleStart(files[0]);
};
const handleMouseEnter = () => {
if (uploadConfig.enablePaste && !uploadConfig.disabled) {
container.value?.focus({ preventScroll: true });
}
};
const handleDragEnter = () => {
if (uploadConfig.enableDrop && !uploadConfig.disabled) {
isDragOver.value = true;
}
};
const handleDragLeave = () => {
isDragOver.value = false;
};
const handleDrop = (event: DragEvent) => {
isDragOver.value = false;
if (!uploadConfig.enableDrop) {
return;
}
addFile(event.dataTransfer?.files?.[0]);
};
const handlePaste = (event: ClipboardEvent) => {
if (!uploadConfig.enablePaste) {
return;
}
addFile(event.clipboardData?.files?.[0]);
};
const handleChange = (file: UploadFile, list: UploadFile[]) => {
if (file.status === 'ready' && uploadConfig.onAddItem) {
fileItem.value = file;
isError.value = false;
errorMsg.value = '';
fileName.value = file.name;
isError.value = !uploadConfig.onAddItem(file, list, errorMsg, upload.value);
if (isError.value) {
fileName.value = '';
} else {
if (uploadConfig.onAddItemSuccess) {
uploadConfig.onAddItemSuccess(file);
}
}
}
};
const handlePreview = (file: UploadFile) => {
if (uploadConfig.onPreview) {
uploadConfig.onPreview(file);
}
};
const handleRemove = (file: UploadFile, list: UploadFile[]) => {
if (uploadConfig.onRemove) {
uploadConfig.onRemove(file, list);
}
};
const handleProgress = (event: any, file: UploadFile, list: UploadFile[]) => {
if (uploadConfig.onProgress) {
uploadConfig.onProgress(event, file, list);
}
};
const handleBeforeUpload = (file: UploadFile) => {
if (uploadConfig.onBeforeUpload) {
const result = Boolean(uploadConfig.onBeforeUpload(file)) || false;
return !result;
}
return true;
};
const handleBeforeRemove = (file: UploadFile, list: UploadFile[]) => {
if (uploadConfig.onBeforeRemove) {
uploadConfig.onBeforeRemove(file, list);
}
};
const clearSelectedFile = () => {
if (fileItem.value !== null) {
upload.value.handleRemove(fileItem.value);
}
fileItem.value = null;
fileName.value = '';
isError.value = false;
errorMsg.value = '';
};
const customUpload = (options: any): Promise<any> => {
return new Promise((resolve, reject) => {
let header = {
headers: {}
};
header.headers = uploadConfig.headers;
const url = uploadConfig.action as string;
const param = options.formData;
if (uploadConfig.isCustomUpload) {
$http
.post(url, param, header)
.then(res => {
resolve(res);
})
.catch(error => {
reject(error);
});
} else {
reject(new Error('failed'));
}
});
};
function trigger(): void {
const input = upload.value.$el.querySelector('input[type=file]');
input.value = '';
input.click();
}
defineExpose({
clear: () => {
clearSelectedFile();
},
setError: (msg: string) => {
isError.value = true;
errorMsg.value = msg;
},
clearError: () => {
isError.value = false;
errorMsg.value = '';
},
customUpload
});
</script>
<template>
<div
ref="container"
class="file-upload"
:class="{ 'drag-over': isDragOver }"
:style="uploadConfig.type !== 'button' ? style : {}"
:tabindex="uploadConfig.enablePaste ? 0 : undefined"
@mouseenter="handleMouseEnter"
@dragenter.prevent="handleDragEnter"
@dragover.prevent="handleDragEnter"
@dragleave.prevent="handleDragLeave"
@drop.prevent="handleDrop"
@paste="handlePaste"
>
<el-input
v-if="uploadConfig.type !== 'button'"
v-model="fileName"
class="upload-display-input"
:placeholder="
uploadConfig.placeholder ||
(uploadConfig.enableDrop || uploadConfig.enablePaste
? $t('ADD_FILE')
: $t('ADD_FILE_CLICK'))
"
:readonly="true"
:class="{ 'error-input': isError && !uploadConfig.isNeedClearError }"
:disabled="uploadConfig.disabled"
@click="trigger"
/>
<el-button
v-if="fileName && uploadConfig.type !== 'button' && !uploadConfig.disabled"
class="upload-clear"
text
@click.stop="clearSelectedFile"
>
<el-icon>
<IconXSolid />
</el-icon>
</el-button>
<el-upload
:id="uploadConfig.id || 'fileUpload'"
ref="upload"
:class="{ 'input-upload-selector': uploadConfig.type !== 'button' }"
:disabled="uploadConfig.disabled"
:action="uploadConfig.action"
:name="uploadConfig.name"
:limit="1"
:headers="uploadConfig.headers"
:data="uploadConfig.data"
:on-exceed="handleExceed"
:on-change="handleChange"
:on-success="handleSuccess"
:on-preview="handlePreview"
:on-remove="handleRemove"
:on-error="handleError"
:on-progress="handleProgress"
:before-upload="handleBeforeUpload"
:before-remove="handleBeforeRemove"
:auto-upload="uploadConfig.isAutoUpload"
list-type="text"
:accept="uploadConfig.accept"
>
<template #trigger>
<el-button
v-if="uploadConfig.type === 'button'"
class="upload-button"
:class="{ disabled: uploadConfig.disabled }"
:disabled="uploadConfig.disabled"
>
<el-icon>
<IconSUpload />
</el-icon>
{{ $t('COMMON_UPLOAD') }}
</el-button>
<el-button v-else class="upload-more-button" :disabled="uploadConfig.disabled">
<el-icon>
<IconMore />
</el-icon>
</el-button>
</template>
<span></span>
</el-upload>
<span v-if="isError && errorMsg" :id="uploadConfig.errorMsgId" class="error-text">
{{ errorMsg }}
</span>
</div>
</template>
<style lang="scss" scoped>
.file-upload {
position: relative;
display: inline-block;
.error-text {
display: inline-block;
color: var(--o-color-danger);
margin-top: 4px;
line-height: 16px;
}
.upload-button {
border: none;
color: var(--o-text-color-secondary);
background-color: var(--o-bg-color-base);
}
.upload-button:hover {
color: var(--o-button-color_hover);
}
&.drag-over {
:deep(.upload-display-input .el-input__inner) {
border-color: var(--o-input-border-color_hover);
box-shadow: 0 0 0 1px var(--o-input-border-color_hover) inset;
}
}
.disabled,
.disabled:hover {
color: var(--o-button-color_disabled) !important;
}
:deep(.el-input) {
.el-input__inner {
padding-top: 8px;
padding-bottom: 8px;
padding-right: 56px;
}
}
.upload-display-input {
width: 100%;
}
.input-upload-selector {
position: absolute;
top: 0;
right: 0;
width: 32px;
height: 32px;
z-index: 2;
}
.upload-more-button {
min-width: 32px;
width: 32px;
height: 32px;
padding: 0;
border: none;
color: var(--o-text-color-secondary);
background: transparent;
}
.upload-more-button:hover {
color: var(--o-input-border-color_hover);
background: transparent;
}
.upload-clear {
position: absolute;
top: -7px;
right: -7px;
z-index: 3;
min-width: 16px;
width: 16px;
height: 16px;
padding: 0;
border: 1px solid var(--o-border-color-base);
border-radius: 50%;
color: var(--o-text-color-secondary);
background: var(--o-bg-color-base);
}
.upload-clear:hover {
color: var(--o-color-danger);
}
}
</style>
<style lang="scss">
.file-upload {
.el-upload {
display: flex;
}
.input-upload-selector {
.el-upload {
width: 100%;
height: 100%;
}
}
.el-upload-list {
display: none;
}
.error-input {
.el-input__inner {
background: var(--o-color-danger-secondary);
border-color: var(--o-form-item-border-color_error);
box-shadow: 0 0 0 1px var(--el-color-danger) inset;
}
.el-input__inner:focus {
border-color: var(--o-form-item-border-color_error);
}
}
}
</style>