ut-upload.vue 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. <template>
  2. <u-upload :width="width" :height="height" :fileList="fileList" :accept="accept" :uploadIcon="uploadIcon"
  3. :uploadText="uploadText" @afterRead="afterRead" @delete="deletePic" :multiple="multiple"
  4. :maxCount="maxCount">
  5. </u-upload>
  6. </template>
  7. <script setup lang="ts">
  8. import upload from '@/utils/upload';
  9. interface FileItem {
  10. url: string;
  11. }
  12. interface UploadEvent {
  13. file: Array<{ url: string }>;
  14. }
  15. interface DeleteEvent {
  16. index: number;
  17. }
  18. interface Props {
  19. modelValue: string | string[] | Record<string, any> | null;
  20. maxCount: number;
  21. width: string;
  22. height: string;
  23. multiple: boolean;
  24. uploadText: string;
  25. uploadIcon: string;
  26. accept: string;
  27. isArr: boolean;
  28. keyUrl: string;
  29. isObject: boolean;
  30. }
  31. const props = withDefaults(defineProps<Props>(), {
  32. modelValue: () => [],
  33. maxCount: 1,
  34. width: '200rpx',
  35. height: '200rpx',
  36. multiple: true,
  37. uploadText: '点击上传',
  38. uploadIcon: 'plus',
  39. accept: 'image',
  40. isArr: false,
  41. keyUrl: '',
  42. isObject: false
  43. });
  44. const emit = defineEmits<{
  45. change: [value: any];
  46. 'update:modelValue': [value: any];
  47. }>();
  48. const fileList = ref<FileItem[]>([]);
  49. watch(() => props.modelValue, (ov) => {
  50. if (ov) {
  51. if (props.isObject) {
  52. fileList.value = [ov as FileItem];
  53. } else if (props.isArr) {
  54. if (props.keyUrl) {
  55. fileList.value = (ov as any[]).map(url => ({ url: url[props.keyUrl] }));
  56. } else {
  57. fileList.value = (ov as string[]).map(url => ({ url }));
  58. }
  59. } else {
  60. const list = (ov as string).split(',');
  61. fileList.value = list.map(item => ({
  62. url: item
  63. }));
  64. }
  65. }
  66. });
  67. const deletePic = (event: DeleteEvent) => {
  68. fileList.value.splice(event.index, 1);
  69. const urls = fileList.value.map(({ url }) => url);
  70. const imgs = urls.toString();
  71. if (props.isObject) {
  72. emit('update:modelValue', null);
  73. emit('change', null);
  74. } else if (props.isArr) {
  75. if (props.keyUrl) {
  76. emit('update:modelValue', fileList.value.map(({ url }) => ({ [props.keyUrl]: url })));
  77. emit('change', fileList.value.map(({ url }) => ({ [props.keyUrl]: url })));
  78. } else {
  79. emit('update:modelValue', urls);
  80. emit('change', urls);
  81. }
  82. } else {
  83. emit('update:modelValue', imgs);
  84. emit('change', imgs);
  85. }
  86. };
  87. const afterRead = async (event: UploadEvent) => {
  88. const files = event.file;
  89. console.log(files);
  90. // 先使用本地临时路径进行预览展示
  91. const startIndex = fileList.value.length;
  92. const tempItems = files.map(({ url }) => ({ url }));
  93. fileList.value = fileList.value.concat(tempItems);
  94. // 同步上传并在成功后用服务端URL替换临时预览
  95. const promises = files.map(({ url }) => upload({
  96. filePath: url,
  97. url: '/resource/oss/upload'
  98. }));
  99. try {
  100. const res = await Promise.all(promises);
  101. const uploaded: FileItem[] = [];
  102. res.forEach(({ code, data }) => {
  103. if (code === 200) {
  104. uploaded.push(data);
  105. } else {
  106. uploaded.push({ url: '' });
  107. }
  108. });
  109. // 替换对应位置的临时项为服务器返回的URL(成功的替换,失败的不动或清理)
  110. uploaded.forEach((item, i) => {
  111. const targetIndex = startIndex + i;
  112. if (item.url) {
  113. fileList.value[targetIndex] = item;
  114. } else {
  115. // 上传失败则移除该临时项
  116. fileList.value.splice(targetIndex, 1);
  117. }
  118. });
  119. const urls = fileList.value.slice(0); // 当前所有成功项
  120. const imgs = urls.map(({ url }) => url).toString();
  121. if (props.isObject) {
  122. emit('update:modelValue', urls[0]);
  123. emit('change', urls[0]);
  124. } else if (props.isArr) {
  125. if (props.keyUrl) {
  126. emit('update:modelValue', urls.map(({ url }) => ({ [props.keyUrl]: url })));
  127. emit('change', urls.map(({ url }) => ({ [props.keyUrl]: url })));
  128. } else {
  129. emit('update:modelValue', urls.map(({ url }) => url));
  130. emit('change', urls.map(({ url }) => url));
  131. }
  132. } else {
  133. emit('update:modelValue', imgs);
  134. emit('change', imgs);
  135. }
  136. } catch (error) {
  137. console.error('Upload failed:', error);
  138. // 全部上传失败则回滚新增的临时项
  139. fileList.value.splice(startIndex, tempItems.length);
  140. }
  141. };
  142. </script>
  143. <style></style>