File.vue 7.1 KB

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