index.vue 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  1. <script setup lang="ts">
  2. import { ElMessage } from 'element-plus'
  3. import type { ImageUploadProps } from './props'
  4. import { imageUploadEmits } from './props'
  5. import type { UploadItem } from './types'
  6. import { uuid } from '@/utils/utils'
  7. import axios, { type AxiosProgressEvent } from 'axios'
  8. const emits = defineEmits(imageUploadEmits)
  9. const props = withDefaults(defineProps<ImageUploadProps>(), {
  10. fileName: 'file',
  11. autoUpload: true,
  12. preview: true,
  13. limit: 9,
  14. fileSize: 5,
  15. iconSize: 28
  16. })
  17. const images = ref<UploadItem[]>([])
  18. // 是否可上传
  19. const isUpload = computed<boolean>(() => {
  20. return (
  21. !props.disabled &&
  22. !(typeof props.limit === 'number' && props.limit > 0 && images.value != null && images.value.length >= props.limit)
  23. )
  24. })
  25. // 检查图片是否全部上传完毕
  26. const checkUpload = () => {
  27. return images.value.length == 0 || (images.value.length && !images.value.some(x => x.status != 'success'))
  28. }
  29. // 预览图片列表
  30. const previewList = computed(() => {
  31. if (!props.preview) {
  32. return []
  33. }
  34. return images.value?.map(x => x.url) || []
  35. })
  36. /* 选择文件 */
  37. const onUpload = (file: File) => {
  38. if (!isUpload.value || props.disabled) {
  39. return false
  40. }
  41. if (!file.type.startsWith('image')) {
  42. ElMessage.error('只能选择图片')
  43. return false
  44. }
  45. if (file.size / 1024 / 1024 > props.fileSize) {
  46. ElMessage.error(`大小不能超过 ${props.fileSize}MB`)
  47. return false
  48. }
  49. const item: UploadItem = {
  50. key: uuid(),
  51. name: file.name,
  52. status: void 0,
  53. progress: 0,
  54. file
  55. }
  56. item.url = window.URL.createObjectURL(file)
  57. images.value.push(item)
  58. // 是否自动上传
  59. if (props.autoUpload) {
  60. // 自动上传最后一个文件
  61. uploadItem(images.value.at(-1) as UploadItem)
  62. }
  63. return false
  64. }
  65. /* 上传文件 */
  66. const uploadItem = (item: UploadItem) => {
  67. if (!props.action && typeof props.uploadFunction != 'function') {
  68. console.log('请传入action路径或者uploadFunction上传方法')
  69. return false
  70. }
  71. // 如果配置了上传路径,TODO:awint
  72. if (props.action) {
  73. const formData = new FormData()
  74. formData.append(props.fileName, item.file as File)
  75. axios
  76. .post(props.action, formData, {
  77. onUploadProgress: (e: AxiosProgressEvent) => {
  78. if (e.total != null) {
  79. item.progress = (e.loaded / e.total) * 100
  80. }
  81. }
  82. })
  83. .then(res => {
  84. item.status = 'success'
  85. item.url = res.data
  86. })
  87. .catch(() => {
  88. item.status = 'danger'
  89. })
  90. } else {
  91. // 自定义方法上传
  92. if (typeof props.uploadFunction != 'function') return false
  93. ;(props.uploadFunction as Function)(item)
  94. }
  95. }
  96. /* 手动上传文件 */
  97. const submit = () => {
  98. if (images.value.length) {
  99. images.value.forEach(item => {
  100. item.status != 'success' && uploadItem(item)
  101. })
  102. }
  103. }
  104. /* 删除图片 */
  105. const onRemove = (index: number) => {
  106. emits('remove', images.value[index])
  107. images.value.splice(index, 1)
  108. }
  109. /* 重新上传 */
  110. const onRetry = (index: number) => {
  111. uploadItem(images.value[index])
  112. }
  113. /* 修改modelValue */
  114. const updateModelValue = (items: any) => {
  115. emits('update:modelValue', items.map((x: UploadItem) => x.url).join(',') || '')
  116. }
  117. watch(
  118. () => props.modelValue,
  119. () => {
  120. // 判断modelValue存不存在,存在就是成功,不存在就没有
  121. if (props.modelValue && props.modelValue != undefined) {
  122. // 传进来的数据转换成内部需要数据
  123. const formatData = typeof props.modelValue === 'string' ? props.modelValue.split(',').filter(x => x) : []
  124. const datas: UploadItem[] = formatData.map((x: string) => {
  125. return {
  126. key: uuid(),
  127. url: x,
  128. status: 'success',
  129. name: x.split('/').pop()
  130. }
  131. })
  132. images.value = datas
  133. }
  134. },
  135. { immediate: true }
  136. )
  137. watch(
  138. images,
  139. () => {
  140. updateModelValue(images.value)
  141. },
  142. {
  143. deep: true
  144. }
  145. )
  146. defineExpose({
  147. checkUpload,
  148. submit
  149. })
  150. </script>
  151. <template>
  152. <div class="upload-container">
  153. <div class="upload-image" v-for="(item, index) in images" :key="item.key">
  154. <el-image :src="item.url" :preview-src-list="previewList" :initial-index="index" fit="cover"></el-image>
  155. <div v-if="!disabled" class="upload-remove" @click.stop="onRemove(index)">
  156. <el-icon size="14">
  157. <Close />
  158. </el-icon>
  159. </div>
  160. <div v-if="item.status === 'uploading' || item.status === 'danger'" class="upload-progress">
  161. <slot name="progress" :item="item">
  162. <div class="upload-text">
  163. {{ item.status == 'danger' ? '上传失败' : '上传中' }}
  164. </div>
  165. <el-progress
  166. :showText="false"
  167. v-bind="progressProps || {}"
  168. :percentage="item.progress"
  169. :status="item.status === 'danger' ? 'exception' : void 0"
  170. />
  171. <div v-if="!disabled">
  172. <div v-if="item.status === 'danger'" class="upload-retry" @click.stop="onRetry(index)">
  173. <el-icon>
  174. <Refresh />
  175. </el-icon>
  176. </div>
  177. </div>
  178. </slot>
  179. </div>
  180. </div>
  181. <div>
  182. <el-upload
  183. action=""
  184. accept="image/*"
  185. :multiple="multiple"
  186. :disabled="disabled"
  187. :show-file-list="false"
  188. :beforeUpload="onUpload"
  189. :drag="drag"
  190. v-if="isUpload"
  191. >
  192. <div class="upload-plus">
  193. <el-icon :size="iconSize">
  194. <Plus />
  195. </el-icon>
  196. </div>
  197. </el-upload>
  198. </div>
  199. </div>
  200. </template>
  201. <style lang="scss" scoped>
  202. .upload-container {
  203. display: flex;
  204. flex-wrap: wrap;
  205. .upload-image {
  206. position: relative;
  207. width: 100px;
  208. height: 100px;
  209. border: 1px dashed #dcdfe6;
  210. border-radius: var(--el-border-radius-base);
  211. overflow: hidden;
  212. margin: 0px 8px 8px 0;
  213. cursor: pointer;
  214. .upload-remove {
  215. width: 20px;
  216. height: 20px;
  217. display: flex;
  218. justify-content: center;
  219. align-items: center;
  220. position: absolute;
  221. right: 0px;
  222. top: 0px;
  223. background-color: rgba($color: #000000, $alpha: 0.3);
  224. color: #fff;
  225. border-radius: 0px 0px 0px var(--el-border-radius-round);
  226. z-index: 9;
  227. &:hover {
  228. background-color: var(--el-color-danger);
  229. }
  230. .el-icon {
  231. margin-top: -5px;
  232. margin-left: 2px;
  233. }
  234. }
  235. .upload-progress {
  236. position: absolute;
  237. width: 100%;
  238. height: 100%;
  239. left: 0px;
  240. top: 0px;
  241. display: flex;
  242. flex-direction: column;
  243. justify-content: center;
  244. padding: 10px;
  245. background-color: rgba($color: #000000, $alpha: 0.4);
  246. color: #fff;
  247. box-sizing: border-box;
  248. .upload-text {
  249. text-align: center;
  250. margin-bottom: 3px;
  251. font-size: 12px;
  252. }
  253. .upload-retry {
  254. text-align: center;
  255. margin-top: 5px;
  256. }
  257. }
  258. .el-image {
  259. width: 100%;
  260. height: 100%;
  261. }
  262. }
  263. .upload-plus {
  264. width: 100px;
  265. height: 100px;
  266. display: flex;
  267. justify-content: center;
  268. align-items: center;
  269. color: var(--el-color-info);
  270. border: 1px dashed #dcdfe6;
  271. border-radius: var(--el-border-radius-base);
  272. &:not(.disabled):hover {
  273. border-color: var(--el-color-primary);
  274. }
  275. }
  276. :deep(.el-upload-dragger) {
  277. width: auto;
  278. height: auto;
  279. padding: 0px;
  280. margin: 0px;
  281. border-color: transparent;
  282. border-width: 0px;
  283. border-radius: 0px;
  284. background-color: transparent;
  285. &.is-dragover {
  286. outline: 1px dashed var(--el-color-primary);
  287. background-color: var(--el-color-primary-light-9);
  288. }
  289. }
  290. }
  291. </style>