index.vue 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. <!--
  2. * 文件上传
  3. *
  4. * @Author: DCCloud
  5. * @Date: 2022-08-12 20:19:39
  6. *
  7. -->
  8. <template>
  9. <div class="clearfix">
  10. <a-upload
  11. multiple
  12. :accept="props.accept"
  13. :before-upload="beforeUpload"
  14. :customRequest="customRequest"
  15. :file-list="fileList"
  16. :headers="{ 'x-access-token': useUserStore().getToken }"
  17. :list-type="listType"
  18. @change="handleChange"
  19. @preview="handlePreview"
  20. @remove="handleRemove"
  21. :showUploadList="showUploadList"
  22. :disabled="disabled"
  23. >
  24. <div v-if="fileList.length < props.maxUploadSize && !slot.customUploadBtnSlot">
  25. <template v-if="listType === 'picture-card'">
  26. <PlusOutlined />
  27. <div class="ant-upload-text">
  28. {{ buttonText }}
  29. </div>
  30. </template>
  31. <template v-if="listType === 'text'">
  32. <a-button :type="btnType">
  33. <upload-outlined v-if="btnIcon" />
  34. {{ buttonText }}
  35. </a-button>
  36. </template>
  37. </div>
  38. <slot name="customUploadBtnSlot"></slot>
  39. </a-upload>
  40. <a-modal :footer="null" :open="previewVisible" @cancel="handleCancel">
  41. <img :src="previewUrl" alt="example" style="width: 100%" />
  42. </a-modal>
  43. </div>
  44. </template>
  45. <script setup>
  46. import { computed, ref, useSlots, watch } from 'vue';
  47. import { Modal } from 'ant-design-vue';
  48. import { fileApi } from '/src/api/support/file-api';
  49. import { useUserStore } from '/@/store/modules/system/user';
  50. import { SmartLoading } from '/@/components/framework/smart-loading';
  51. import { FILE_FOLDER_TYPE_ENUM } from '/@/constants/support/file-const';
  52. import { smartSentry } from '/@/lib/smart-sentry';
  53. const props = defineProps({
  54. value: String,
  55. buttonText: {
  56. type: String,
  57. default: '点击上传附件',
  58. },
  59. showUploadBtn: {
  60. type: Boolean,
  61. default: true,
  62. },
  63. defaultFileList: {
  64. type: Array,
  65. default: () => [],
  66. },
  67. multiple: {
  68. type: Boolean,
  69. default: false,
  70. },
  71. // 最多上传文件数量
  72. maxUploadSize: {
  73. type: Number,
  74. default: 10,
  75. },
  76. maxSize: {
  77. type: Number,
  78. default: 500,
  79. },
  80. // 上传的文件类型
  81. accept: {
  82. type: String,
  83. default: '',
  84. },
  85. // 文件上传类型
  86. folder: {
  87. type: Number,
  88. default: FILE_FOLDER_TYPE_ENUM.COMMON.value,
  89. },
  90. // 上传列表的内建样式,支持三种基本样式 text, picture 和 picture-card
  91. listType: {
  92. type: String,
  93. default: 'picture-card',
  94. },
  95. // 上传按钮样式
  96. btnType: {
  97. type: String,
  98. default: 'default',
  99. },
  100. // 是否显示上传按钮图标
  101. btnIcon: {
  102. type: Boolean,
  103. default: true,
  104. },
  105. //是否显示上传列表
  106. showUploadList: {
  107. type: Boolean,
  108. default: true,
  109. },
  110. //是否上传后清空文件列表
  111. uploadAterClear: {
  112. type: Boolean,
  113. default: false,
  114. },
  115. //是否禁用上传
  116. disabled: {
  117. type: Boolean,
  118. default: false,
  119. },
  120. });
  121. const slot = useSlots();
  122. // 图片类型的后缀名
  123. const imgFileType = ['jpg', 'jpeg', 'png', 'gif'];
  124. // 重新修改图片展示字段
  125. const files = computed(() => {
  126. let res = [];
  127. if (props.defaultFileList && props.defaultFileList.length > 0) {
  128. props.defaultFileList.forEach((element) => {
  129. element.url = element.fileUrl;
  130. element.name = element.fileName;
  131. res.push(element);
  132. });
  133. return res;
  134. }
  135. return res;
  136. });
  137. // -------------------- 逻辑 --------------------
  138. const previewVisible = ref(false);
  139. const fileList = ref([]);
  140. const previewUrl = ref('');
  141. watch(
  142. files,
  143. (value) => {
  144. fileList.value = value;
  145. },
  146. {
  147. immediate: true,
  148. }
  149. );
  150. const emit = defineEmits(['update:value', 'change']);
  151. const customRequest = async (options) => {
  152. SmartLoading.show();
  153. try {
  154. // console.log(options);
  155. const formData = new FormData();
  156. formData.append('file', options.file);
  157. // console.log(formData)
  158. let res = await fileApi.uploadFile(formData, props.folder);
  159. // console.log(res)
  160. let file = res.data;
  161. file.url = file.fileUrl;
  162. file.name = file.fileName;
  163. if (props.uploadAterClear) {
  164. fileList.value = [];
  165. }
  166. fileList.value.push(file);
  167. emit('change', fileList.value);
  168. } catch (e) {
  169. smartSentry.captureError(e);
  170. } finally {
  171. SmartLoading.hide();
  172. }
  173. };
  174. function handleChange(info) {
  175. // console.log(info)
  176. let fileStatus = info.file.status;
  177. let file = info.file;
  178. if (fileStatus === 'removed') {
  179. let index = fileList.value.findIndex((e) => e.fileId === file.fileId);
  180. if (index !== -1) {
  181. fileList.value.splice(index, 1);
  182. emit('change', fileList.value);
  183. }
  184. }
  185. }
  186. function handleRemove(file) {
  187. // console.log(fileList.value);
  188. }
  189. function beforeUpload(file, files) {
  190. if (fileList.value.length + files.length > props.maxUploadSize) {
  191. showErrorMsgOnce(`最多支持上传 ${props.maxUploadSize} 个文件哦!`);
  192. return false;
  193. }
  194. if (props.accept) {
  195. const suffixIndex = file.name.lastIndexOf('.');
  196. const fileSuffix = file.name.substring(suffixIndex <= -1 ? 0 : suffixIndex);
  197. if (props.accept.indexOf(fileSuffix) === -1) {
  198. showErrorMsgOnce(`只支持上传 ${props.accept.replaceAll(',', ' ')} 格式的文件`);
  199. return false;
  200. }
  201. }
  202. const isLimitSize = file.size / 1024 / 1024 < props.maxSize;
  203. if (!isLimitSize) {
  204. showErrorMsgOnce(`单个文件大小必须小于 ${props.maxSize} Mb`);
  205. }
  206. return isLimitSize;
  207. }
  208. const showErrorModalFlag = ref(true);
  209. const showErrorMsgOnce = (content) => {
  210. if (showErrorModalFlag.value) {
  211. Modal.error({
  212. title: '提示',
  213. content: content,
  214. okType: 'danger',
  215. centered: true,
  216. onOk() {
  217. showErrorModalFlag.value = true;
  218. },
  219. });
  220. showErrorModalFlag.value = false;
  221. }
  222. };
  223. function handleCancel() {
  224. previewVisible.value = false;
  225. }
  226. const handlePreview = async (file) => {
  227. if (imgFileType.some((e) => e === file.fileType)) {
  228. previewUrl.value = file.url || file.preview;
  229. previewVisible.value = true;
  230. } else {
  231. fileApi.downLoadFile(file.fileKey);
  232. }
  233. };
  234. // ------------------------ 清空 上传 ------------------------
  235. function clear() {
  236. fileList.value = [];
  237. }
  238. defineExpose({
  239. clear,
  240. });
  241. </script>
  242. <style lang="less" scoped>
  243. :deep(.ant-upload-picture-card-wrapper) {
  244. display: flex;
  245. }
  246. </style>