index.vue 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. <template>
  2. <div class="file-upload">
  3. <el-upload
  4. ref="uploadRef"
  5. :action="uploadUrl"
  6. :headers="headers"
  7. :data="uploadDataDict"
  8. :show-file-list="false"
  9. :auto-upload="autoUpload"
  10. :http-request="customRequest"
  11. :disabled="disabled"
  12. :before-upload="handleBeforeUpload"
  13. :accept="accept"
  14. :multiple="multiple"
  15. :on-exceed="handleExceededSize"
  16. @change="handleFileChange"
  17. @error="handleError"
  18. @success="handleSuccess"
  19. >
  20. <template #trigger>
  21. <el-button type="primary" :disabled="loading">{{ btnText }}</el-button>
  22. <!-- <el-button
  23. v-if="!autoUpload"
  24. type="primary"
  25. :loading="loading"
  26. :disabled="!canUpload"
  27. @click.stop="startUpload"
  28. >开始上传</el-button
  29. > -->
  30. </template>
  31. </el-upload>
  32. </div>
  33. </template>
  34. <script setup lang="ts">
  35. import { ref } from 'vue';
  36. import { fileMD5 } from '@/utils/md5';
  37. import { ElMessage } from 'element-plus';
  38. import type {
  39. UploadProps,
  40. UploadRequestOptions,
  41. UploadFile,
  42. UploadFiles,
  43. UploadRawFile,
  44. UploadProgressEvent,
  45. } from 'element-plus';
  46. import axios, { AxiosHeaders } from 'axios';
  47. defineOptions({
  48. name: 'UploadButton',
  49. });
  50. const props = withDefaults(
  51. defineProps<{
  52. uploadUrl: string;
  53. uploadData?: Record<string, any>;
  54. maxSize?: number;
  55. uploadFileAlias?: string;
  56. autoUpload?: boolean;
  57. disabled?: boolean;
  58. btnText?: string;
  59. accept?: string;
  60. multiple?: boolean;
  61. }>(),
  62. {
  63. uploadUrl: '',
  64. uploadData: () => {
  65. return {};
  66. },
  67. maxSize: 20 * 1024 * 1024,
  68. uploadFileAlias: 'file',
  69. autoUpload: true,
  70. disabled: false,
  71. btnText: '选择',
  72. accept: '.xls,.xlsx',
  73. multiple: false,
  74. }
  75. );
  76. const emit = defineEmits([
  77. 'uploading',
  78. 'uploadError',
  79. 'uploadSuccess',
  80. 'validError',
  81. ]);
  82. const uploadRef = ref();
  83. const attachmentName = ref('');
  84. const canUpload = ref(false);
  85. const uploadDataDict = ref({});
  86. const headers = ref({ md5: '' });
  87. const result = ref({ success: true, message: '' });
  88. const loading = ref(false);
  89. function startUpload() {
  90. loading.value = true;
  91. uploadRef.value?.submit();
  92. }
  93. function handleFileChange(uploadFile: UploadFile, uploadFiles: UploadFiles) {
  94. if (props.autoUpload || !uploadFiles.length) return;
  95. // Element Plus's status: ready, uploading, success, fail
  96. // Arco's status: init, uploading, success, error
  97. // We'll assume 'ready' is similar to 'init' for this logic, though direct mapping might not be perfect.
  98. canUpload.value = uploadFiles[0]?.status === 'ready';
  99. }
  100. async function handleBeforeUpload(rawFile: UploadRawFile) {
  101. uploadDataDict.value = {
  102. ...props.uploadData,
  103. filename: rawFile.name,
  104. };
  105. if (rawFile.size > props.maxSize) {
  106. // Element Plus uses on-exceed for this, but we can also check here
  107. // For consistency with original logic, we call handleExceededSize and reject
  108. // However, on-exceed will also be triggered if :limit is set for file count, not size directly.
  109. // Here, we manually trigger the message and prevent upload.
  110. const content = `文件大小不能超过${Math.floor(
  111. props.maxSize / 1024 / 1024
  112. )}MB`;
  113. ElMessage.error(content);
  114. result.value = {
  115. success: false,
  116. message: content,
  117. };
  118. emit('validError', result.value);
  119. return Promise.reject(new Error(content));
  120. }
  121. const md5 = await fileMD5(rawFile);
  122. headers.value.md5 = md5;
  123. if (props.autoUpload) loading.value = true;
  124. return true;
  125. }
  126. function customRequest(options: UploadRequestOptions) {
  127. // Changed return type to any
  128. const { onProgress, file, data } = options;
  129. const formData = new FormData();
  130. const paramData: Record<string, any> = data || {};
  131. Object.entries(paramData).forEach(([k, v]) => {
  132. formData.append(k, v);
  133. });
  134. formData.append(props.uploadFileAlias, file as File);
  135. emit('uploading');
  136. return axios.post(options.action, formData, {
  137. headers: options.headers as AxiosHeaders,
  138. onUploadProgress: ({ loaded, total }) => {
  139. onProgress({
  140. percent: total ? Math.floor((100 * loaded) / total) : 0,
  141. } as UploadProgressEvent);
  142. },
  143. });
  144. }
  145. function handleError(
  146. error: Error,
  147. uploadFile: UploadFile,
  148. uploadFiles: UploadFiles
  149. ) {
  150. canUpload.value = false;
  151. loading.value = false;
  152. result.value = {
  153. success: false,
  154. message: error.message || '上传失败',
  155. };
  156. ElMessage.error(result.value.message);
  157. emit('uploadError', result.value);
  158. }
  159. function handleSuccess(
  160. response: any,
  161. uploadFile: UploadFile,
  162. uploadFiles: UploadFiles
  163. ) {
  164. canUpload.value = false;
  165. loading.value = false;
  166. result.value = {
  167. success: true,
  168. message: '上传成功!',
  169. };
  170. ElMessage.success('上传成功!');
  171. emit('uploadSuccess', {
  172. ...result.value,
  173. filename: uploadFile.name,
  174. response,
  175. });
  176. }
  177. function handleExceededSize(files: File[]) {
  178. // This function is now primarily called by el-upload's on-exceed prop
  179. // The before-upload also has a check, this is a fallback or for multiple files if `limit` (count) is exceeded.
  180. const file = files[0]; // Assuming single file upload context for this message
  181. const content = `文件 ${file.name} 大小超过限制 (${Math.floor(
  182. props.maxSize / 1024 / 1024
  183. )}MB)`;
  184. result.value = {
  185. success: false,
  186. message: content,
  187. };
  188. loading.value = false;
  189. ElMessage.error(content);
  190. emit('validError', result.value);
  191. }
  192. </script>
  193. <style lang="less">
  194. .file-upload {
  195. display: flex;
  196. justify-content: space-between;
  197. align-items: center;
  198. width: 100%;
  199. .arco-upload-hide {
  200. display: inline-block;
  201. }
  202. .el-input__wrapper {
  203. flex-grow: 2;
  204. margin-right: 10px;
  205. }
  206. .arco-upload {
  207. flex-grow: 0;
  208. flex-shrink: 0;
  209. }
  210. }
  211. </style>