index.vue 5.6 KB

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