index.jsx 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. import { ElUpload, ElIcon, ElProgress } from 'element-plus'
  2. import { SuccessFilled, CircleCloseFilled, Paperclip } from '@element-plus/icons-vue'
  3. import './index.less'
  4. import { toRefs, defineComponent, computed } from 'vue'
  5. import CipButton from '@cip/components/cip-button'
  6. export default defineComponent({
  7. name: 'CipUpload',
  8. props: {
  9. uploadFile: { // 文件上传
  10. type: Function,
  11. default: (info) => {}
  12. },
  13. accept: { // 可选择文件类型控制
  14. type: String,
  15. default: '' // 所有
  16. },
  17. beforeRemove: Function, // 删除文件之前的钩子
  18. onRemove: Function, // 文件列表移除文件时的钩子
  19. beforeUpload: Function, // 上传文件之前的钩子
  20. onError: Function, // 文件上传失败时的钩子
  21. onSuccess: Function, // 文件上传成功时的钩子
  22. onPreview: Function, // 点击文件列表中已上传的文件时的钩子
  23. fileList: { type: Array, default: () => [] }, // 默认上传文件
  24. multiple: Boolean, // 是否允许多个
  25. disabled: Boolean, // 禁用状态
  26. drag: Boolean, // 是否拖拽上传
  27. action: String, // 上传地址
  28. limit: Number, // 上传数量限制
  29. showFileList: { // 是否展示默认,后期考虑不用默认的
  30. type: Boolean,
  31. default: true
  32. },
  33. itemStyle: { // 文件列表的行内样式
  34. type: Object,
  35. default: () => ({})
  36. },
  37. listType: { // 展示形式
  38. type: String,
  39. default: 'text'
  40. }
  41. },
  42. emits: ['update:fileList'],
  43. inheritAttrs: false,
  44. setup (props, { slots, emit, attrs }) {
  45. const { fileList } = toRefs(props)
  46. const uploadDisabled = computed(() => props.disabled || props.limit <= fileList.value.length)
  47. function genDefault () {
  48. if (props.drag) { // 拖拽
  49. return <div class="cip-upload-drag-trigger">
  50. <i class="el-icon-upload" style="font-size: 50px"></i>
  51. <div>
  52. 点击或将文件拖拽到这里上传
  53. </div>
  54. <div class={'el-upload__tip'}>{slots.tip?.()}</div>
  55. </div>
  56. } else if (props.listType === 'picture-card') { // 缩略图
  57. return uploadDisabled.value || <div class="cip-upload-picture-trigger" style={ props.itemStyle }>
  58. <i class="el-icon-plus"></i>
  59. <div>上传</div>
  60. </div>
  61. } else { // 默认
  62. return <CipButton buttonType="upload" disabled={uploadDisabled.value}></CipButton>
  63. }
  64. }
  65. async function deleteItem (index) {
  66. const uploadFiles = fileList.value.concat()
  67. const file = uploadFiles.splice(index, 1)[0]
  68. let flag
  69. try {
  70. flag = await props.beforeRemove?.(file, uploadFiles)
  71. } catch (err) {
  72. flag = props.beforeRemove?.(file, uploadFiles)
  73. }
  74. // 如果beforeRemove函数不存在 或者 返回值为true
  75. if (flag || !props.beforeRemove) {
  76. emit('update:fileList', uploadFiles)
  77. props.onRemove?.(file, uploadFiles)
  78. }
  79. }
  80. function genUpload (slots) {
  81. return <ElUpload
  82. {...attrs}
  83. fileList={props.fileList}
  84. onUpdate:fileList={list => emit('update:fileList', list)}
  85. class={'cip-upload-content'}
  86. action={props.action}
  87. httpRequest={httpRequest}
  88. beforeUpload={props.beforeUpload}
  89. multiple={props.multiple}
  90. disabled={props.disabled}
  91. accept={props.accept}
  92. drag={props.drag}
  93. show-file-list={false}
  94. >
  95. {{
  96. // 1. default或者trigger存在,不渲染默认;渲染对应的default或者trigger;trigger在下面一行渲染
  97. default: () => slots.default?.() || (slots.trigger ? undefined : genDefault()),
  98. // 这个trigger要这么写,如果给它赋值函数,会出bug
  99. trigger: slots.trigger ? () => slots.trigger() : undefined,
  100. tip: () => props.drag || <div class={'el-upload__tip'} style={{ marginLeft: props.listType === 'picture-card' ? '0' : '12px' }}>{slots.tip?.()}</div>
  101. }}
  102. </ElUpload>
  103. }
  104. function httpRequest (opts) {
  105. function setStatus (status, uploadFile) {
  106. const uid = opts.file.uid
  107. const index = fileList.value.findIndex(file => file.uid === uid)
  108. if (status === 'error') {
  109. Object.assign(fileList.value[index], { status, opts })
  110. } else if (status === 'success') {
  111. Object.assign(fileList.value[index], { status, ...uploadFile, opts: undefined })
  112. }
  113. emit('update:fileList', fileList.value.concat())
  114. }
  115. props.uploadFile(opts)?.then(file => {
  116. setStatus('success', file)
  117. props.onSuccess?.(file, opts.file, fileList.value.concat())
  118. }).catch((err) => {
  119. setStatus('error')
  120. props.onError?.(err, opts.file, fileList.value.concat())
  121. })
  122. }
  123. function reUpload (item) {
  124. item.status = 'ready'
  125. httpRequest(item.opts)
  126. }
  127. function genItems () {
  128. return <>
  129. {genUpload(slots)}
  130. { fileList.value?.length === 0
  131. ? undefined
  132. : <ul class="el-upload-list el-upload-list--text">
  133. {
  134. fileList.value.map((item, index) => <li style={ props.itemStyle } class={['el-upload-list__item', `is-${item.status}`]} key={item.name + index} onClick={() => props.onPreview?.(item)}>
  135. <div class="el-upload-list__item-name">
  136. <ElIcon><Paperclip /></ElIcon>
  137. <span class="el-upload-list__item-file-name">{item.name}</span>
  138. </div>
  139. <div class="el-upload-list__item-label">
  140. {item.status === 'error' && <i class="el-icon-refresh-right" onClick={() => reUpload(item)}></i>}
  141. <ElIcon class="upload--success"><SuccessFilled /></ElIcon>
  142. <i class="el-icon-close upload--close" onClick={() => deleteItem(index)}></i>
  143. </div>
  144. {item.status === 'ready' && <ElProgress stroke-width={2} indeterminate percentage={72} />}
  145. </li>)
  146. }
  147. </ul>}
  148. </>
  149. }
  150. function genPictureCard () {
  151. return <>
  152. <ul class="el-upload-list el-upload-list--picture-card">
  153. {
  154. fileList.value.map((item, index) => <li class="el-upload-list__item" style={ props.itemStyle } key={item.name + index} onClick={() => props.onPreview?.(item)}>
  155. {item.status === 'success' && <img src={item.url} />}
  156. <div class="upload--close">
  157. <ElIcon onClick={() => deleteItem(index)}><CircleCloseFilled /></ElIcon>
  158. </div>
  159. {item.status === 'ready' && <ElProgress stroke-width={4} style="top:50%;width:80%" indeterminate percentage={72} />}
  160. </li>)
  161. }
  162. {uploadDisabled.value || <li class="el-upload el-upload--picture-card" style={ props.itemStyle }>{genUpload({ trigger: slots.trigger, default: slots.default })}</li>}
  163. </ul>
  164. <div class={'el-upload__tip'}>{slots.tip?.()}</div>
  165. </>
  166. }
  167. function genPicture () {
  168. return <>
  169. {genUpload(slots)}
  170. { fileList.value?.length === 0
  171. ? undefined
  172. : <ul class="el-upload-list el-upload-list--picture">
  173. {
  174. fileList.value.map((item, index) => <li key={item.name + index} onClick={() => props.onPreview?.(item)}>
  175. {['jpeg', 'jpg', 'png', 'gif', 'svg'].includes(item.name?.split('.').pop())
  176. ? <img src={item.url} />
  177. : <i class="picture-icon"></i>}
  178. <div class="picture-info">
  179. <div class="item-name">{item.name}</div>
  180. <span class="item-size">{item.size}</span>
  181. </div>
  182. <div class="upload--close">
  183. <ElIcon onClick={() => deleteItem(index)}><CircleCloseFilled /></ElIcon>
  184. </div>
  185. </li>)
  186. }
  187. </ul>}
  188. </>
  189. }
  190. const renderTypeMap = {
  191. text: genItems,
  192. 'picture-card': genPictureCard,
  193. picture: genPicture
  194. }
  195. return () => <div class="cip-upload">
  196. { props.showFileList ? (renderTypeMap[props.listType]?.()) : genUpload(slots) }
  197. </div>
  198. }
  199. })