File.vue 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. <template>
  2. <div class="upload-file">
  3. <el-upload
  4. ref="uploadRef"
  5. :action="uploadFileUrl"
  6. :file-list="_fileList"
  7. class="upload-file-uploader"
  8. :show-file-list="false"
  9. :multiple="true"
  10. :disabled="self_disabled"
  11. :limit="limit"
  12. :before-upload="beforeUpload"
  13. :on-exceed="handleExceed"
  14. :on-success="uploadSuccess"
  15. :on-error="uploadError"
  16. :accept="fileType.join(',')"
  17. :headers="headers"
  18. >
  19. <el-button :icon="icon" type="primary">{{ text }}</el-button>
  20. </el-upload>
  21. <!-- 上传提示 -->
  22. <div class="el-upload__tip" v-if="showTip">
  23. 请上传
  24. <template v-if="fileSize">
  25. 大小不超过 <b style="color: #f56c6c">{{ fileSize }}MB</b>
  26. </template>
  27. <template v-if="fileType">
  28. 格式为 <b style="color: #f56c6c">{{ fileType.join('/') }}</b>
  29. </template>
  30. 的文件
  31. </div>
  32. <!-- 文件列表 -->
  33. <transition-group class="upload-file-list el-upload-list el-upload-list--text" name="el-fade-in-linear" tag="ul">
  34. <li :key="file.uid" class="el-upload-list__item ele-upload-list__item-content" v-for="(file, index) in _fileList">
  35. <el-link :href="`${file.url}`" :underline="false" target="_blank">
  36. <span class="document">
  37. {{ file.name }}
  38. </span>
  39. </el-link>
  40. <div class="ele-upload-list__item-content-action">
  41. <el-link :underline="false" @click="handleRemove(index)" type="danger">删除</el-link>
  42. </div>
  43. </li>
  44. </transition-group>
  45. </div>
  46. </template>
  47. <script setup lang="ts" name="UploadImgs">
  48. import { ref, computed, inject, watch } from 'vue'
  49. import type { UploadProps, UploadFile } from 'element-plus'
  50. import { ElMessage, formContextKey, formItemContextKey, UploadInstance } from 'element-plus'
  51. import { showFullScreenLoading, tryHideFullScreenLoading } from '@/components/Loading/fullScreen'
  52. import { globalHeaders } from '@/api'
  53. import { OssVO } from '@/api/interface/system/oss'
  54. import { getListByIdsApi, delOssApi } from '@/api/modules/system/oss'
  55. import { listToString } from '@/utils/common'
  56. interface UploadFileProps {
  57. modelValue?: string | number
  58. disabled?: boolean // 是否禁用上传组件 ==> 非必传(默认为 false)
  59. drag?: boolean // 是否支持拖拽上传 ==> 非必传(默认为 true)
  60. limit?: number // 最大图片上传数 ==> 非必传(默认为 5张)
  61. fileSize?: number // 图片大小限制 ==> 非必传(默认为 5M)
  62. isShowTip?: boolean // 是否显示提示信息 ==> 非必传(默认为 true)
  63. text?: string // 按钮文字
  64. icon?: string
  65. fileType?: Array<string>
  66. uploadApiPath?: string // 上传文件服务器地址
  67. }
  68. // 默认值
  69. const props = withDefaults(defineProps<UploadFileProps>(), {
  70. modelValue: () => '',
  71. drag: true,
  72. disabled: false,
  73. limit: 1,
  74. fileSize: 5,
  75. fileType: () => ['doc', 'docx', 'xls', 'xlsx', 'ppt', 'txt', 'pdf', 'mp4'],
  76. text: '文件上传',
  77. isShowTip: true,
  78. uploadApiPath: '/common/upload',
  79. icon: 'upload-filled'
  80. })
  81. const baseUrl = import.meta.env.VITE_API_URL
  82. const uploadFileUrl = ref(baseUrl + props.uploadApiPath)
  83. const headers = ref(globalHeaders())
  84. const uploadRef = ref<UploadInstance>()
  85. const number = ref(0)
  86. const showTip = computed(() => props.isShowTip && (props.fileType || props.fileSize))
  87. const uploadList = ref<any[]>([])
  88. const uploadFileListExport = ref<any[]>([])
  89. const emit = defineEmits<{
  90. 'update:modelValue': [value: any]
  91. }>()
  92. // 获取 el-form 组件上下文
  93. const formContext = inject(formContextKey, void 0)
  94. // 获取 el-form-item 组件上下文
  95. const formItemContext = inject(formItemContextKey, void 0)
  96. // 判断是否禁用上传和删除
  97. const self_disabled = computed(() => {
  98. return props.disabled || formContext?.disabled
  99. })
  100. const _fileList = ref<any[]>([])
  101. // 监听 props.modelValue 列表默认值改变
  102. watch(
  103. () => props.modelValue,
  104. async (val: string | number) => {
  105. if (val) {
  106. let temp = 1
  107. // 首先将值转为数组
  108. let list: any[] = []
  109. if (Array.isArray(val)) {
  110. list = val as OssVO[]
  111. } else {
  112. const res = await getListByIdsApi(val)
  113. list = res.data.map(oss => {
  114. return {
  115. name: oss.originalName,
  116. url: oss.url,
  117. ossId: oss.ossId
  118. }
  119. })
  120. }
  121. // 然后将数组转为对象数组
  122. _fileList.value = list.map(item => {
  123. item = { name: item.name, url: item.url, ossId: item.ossId }
  124. item.uid = item.uid || new Date().getTime() + temp++
  125. return item
  126. })
  127. } else {
  128. _fileList.value = []
  129. return []
  130. }
  131. },
  132. { deep: true, immediate: true }
  133. )
  134. /**
  135. * @description 文件上传之前判断
  136. * @param rawFile 选择的文件
  137. * */
  138. const beforeUpload: UploadProps['beforeUpload'] = rawFile => {
  139. // 校验文件格式
  140. const fileName = rawFile.name.split('.')
  141. const fileExt = fileName[fileName.length - 1]
  142. const isTypeOk = props.fileType.indexOf(fileExt) >= 0
  143. // 校检文件大小
  144. const isLt = rawFile.size / 1024 / 1024 < props.fileSize
  145. if (!isTypeOk) {
  146. ElMessage.error(`文件格式不正确, 请上传${props.fileType.join('/')}格式文件!`)
  147. return false
  148. }
  149. if (!isLt) {
  150. ElMessage.error(`文件大小不能超过 ${props.fileSize}M!`)
  151. return false
  152. }
  153. number.value++
  154. showFullScreenLoading('正在上传文件,请稍候...')
  155. return isTypeOk && isLt
  156. }
  157. /**
  158. * @description 文件上传成功
  159. * @param response 上传响应结果
  160. * @param uploadFile 上传的文件
  161. * */
  162. const uploadSuccess = (response: any | undefined, uploadFile: UploadFile) => {
  163. if (response.code !== 200) {
  164. number.value--
  165. ElMessage.error(response.msg)
  166. uploadRef.value?.handleRemove(uploadFile)
  167. uploadedSuccessfully()
  168. return
  169. }
  170. uploadList.value.push({ name: response.data.fileName, url: response.data.url, ossId: response.data.ossId })
  171. uploadFileListExport.value = []
  172. uploadFileListExport.value.push({ name: response.data.fileName, url: response.data.url, ossId: response.data.ossId })
  173. console.log(uploadList.value)
  174. uploadedSuccessfully()
  175. }
  176. // 上传结束处理
  177. const uploadedSuccessfully = () => {
  178. if (number.value > 0 && uploadList.value.length === number.value) {
  179. _fileList.value = _fileList.value.filter(f => f.url !== undefined).concat(uploadList.value)
  180. uploadList.value = []
  181. number.value = 0
  182. console.log('update', listToString(_fileList.value))
  183. emit('update:modelValue', listToString(_fileList.value))
  184. tryHideFullScreenLoading()
  185. }
  186. // 监听表单验证
  187. formItemContext?.prop && formContext?.validateField([formItemContext.prop as string])
  188. }
  189. /**
  190. * @description 删除图片
  191. * @param file 删除的文件
  192. * */
  193. const handleRemove = (index: number) => {
  194. let ossId = _fileList.value[index].ossId
  195. delOssApi(ossId)
  196. _fileList.value.splice(index, 1)
  197. emit('update:modelValue', listToString(_fileList.value))
  198. }
  199. /**
  200. * @description 文件上传错误
  201. * */
  202. const uploadError = () => {
  203. ElMessage.error('文件上传失败,请您重新上传!')
  204. }
  205. /**
  206. * @description 文件数超出
  207. * */
  208. const handleExceed = () => {
  209. ElMessage.warning(`当前最多只能上传 ${props.limit} 个文件 ,请移除后上传!`)
  210. }
  211. defineExpose({
  212. uploadFileListExport
  213. })
  214. </script>
  215. <style scoped lang="scss">
  216. .upload-file-uploader {
  217. margin-bottom: 5px;
  218. }
  219. .upload-file-list .el-upload-list__item {
  220. position: relative;
  221. margin-bottom: 10px;
  222. line-height: 2;
  223. border: 1px solid #e4e7ed;
  224. }
  225. .upload-file-list .ele-upload-list__item-content {
  226. display: flex;
  227. align-items: center;
  228. justify-content: space-between;
  229. color: inherit;
  230. }
  231. .ele-upload-list__item-content-action .el-link {
  232. margin-right: 10px;
  233. }
  234. </style>