index.vue 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365
  1. <template>
  2. <div class="content">
  3. <el-upload
  4. v-loading="isUploading"
  5. :disabled="componentError"
  6. class="upload-demo"
  7. drag
  8. :headers="headers"
  9. :file-list="viewFileList"
  10. :name="name"
  11. :show-file-list="showFileList"
  12. :list-type="showFileListType"
  13. :multiple="multiple"
  14. :action="uploadUrl"
  15. :limit="limit"
  16. :on-success="handleUploadSuccess"
  17. :before-upload="handleBeforeUpload"
  18. :on-error="handleUploadError"
  19. :on-exceed="handleExceed"
  20. :before-remove="handleBeforeRemove"
  21. :on-remove="handleRemove"
  22. :on-preview="handlePreviewOpen"
  23. >
  24. <i class="el-icon-upload"></i>
  25. <div
  26. class="el-upload__text"
  27. v-if="!componentError"
  28. >将文件拖到此处,或<em>点击上传</em></div>
  29. <div
  30. class="error-text"
  31. v-else
  32. > 组件配置错误,请查看控制台
  33. </div>
  34. <div
  35. class="el-upload__tip"
  36. slot="tip"
  37. >
  38. 文件大小不超过{{ limitSize }}m,文件类型为{{ types.toString() }}
  39. </div>
  40. </el-upload>
  41. <!-- 预览 -->
  42. <el-dialog
  43. title="预览"
  44. :visible.sync="previewObj.show"
  45. width="60%"
  46. :before-close="handlePreviewClose"
  47. >
  48. <div class="preview-content">
  49. <template v-if="previewObj.file && previewObj.file.type.includes('image')">
  50. <el-image class="preview-item" :src="previewObj.file.url" />
  51. </template>
  52. <template v-if="previewObj.file && previewObj.file.type.includes('video')">
  53. <video class="preview-item" controls :src="previewObj.file.url" />
  54. </template>
  55. </div>
  56. </el-dialog>
  57. </div>
  58. </template>
  59. <script>
  60. import mime from 'mime'
  61. let fullLoading = null
  62. const baseURL = process.env.VUE_APP_DOMAIN_PREFIX
  63. export default {
  64. name: "Upload",
  65. props: {
  66. headers:{
  67. type:Object,
  68. default:()=>({})
  69. },
  70. /** 上传时候表单的KEY */
  71. name: {
  72. type: String,
  73. default: () => "file"
  74. },
  75. /** 限制上传数量 */
  76. limit: {
  77. type: Number,
  78. default: () => 5
  79. },
  80. /** 限制的那张大小 单位M */
  81. limitSize: {
  82. type: Number,
  83. default: () => 5
  84. },
  85. /** 是否多选 */
  86. multiple: {
  87. type: Boolean,
  88. default: () => true
  89. },
  90. /** 是否展示文件列表 */
  91. showFileList: {
  92. type: Boolean,
  93. default: () => true
  94. },
  95. /** 文件展示方式 text/picture/picture-card */
  96. showFileListType:{
  97. type:String,
  98. default:()=>'text'
  99. },
  100. /** 允许上传的文件尾缀 string[] */
  101. types: {
  102. type: Array,
  103. default: () => (['jpg', 'png', 'gif'])
  104. },
  105. /** 默认的文件列表 string[] */
  106. defaultFileList: {
  107. type: Array,
  108. default: () => ([])
  109. },
  110. /** 上传成功后端返回的字段名称 */
  111. responseFileName: {
  112. type: String,
  113. default: () => 'url'
  114. },
  115. /** 是否需要全屏loading */
  116. needFullScreenLoading:{
  117. type:Boolean,
  118. default:()=>true
  119. }
  120. },
  121. data() {
  122. return {
  123. uploadUrl: `${ baseURL }/file/upload`,
  124. // 真实文件列表
  125. fileList: [],
  126. // 默认展示的list,解决多上传只回调success一次的问题
  127. viewFileList:[],
  128. // 组件是否部署错误
  129. componentError: false,
  130. // 是否正在上传
  131. isUploading:false,
  132. // 预览对象
  133. previewObj:{
  134. show:false,
  135. file:null
  136. }
  137. }
  138. },
  139. watch: {
  140. defaultFileList: {
  141. handler() {
  142. // 判断类型
  143. const flag = Object.prototype.toString.call(this.defaultFileList) === '[object Array]'
  144. && this.defaultFileList.length > 0 &&
  145. Object.prototype.toString.call(this.defaultFileList[0]) !== '[object String]'
  146. if (flag) {
  147. this.componentError = true
  148. throw new Error('defaultFileList格式错误,应为string[]格式')
  149. }else{
  150. this.componentError = false
  151. }
  152. this.viewFileList = this.defaultFileList.map(defaultFilePath => ({name: defaultFilePath, url: defaultFilePath}))
  153. this.viewFileList.forEach(item=>{
  154. this.fileList.push(item)
  155. })
  156. },
  157. deep: true,
  158. immediate: true
  159. },
  160. fileList:{
  161. handler(){
  162. this.handleNotifyFather()
  163. },
  164. deep:true,
  165. immediate:false
  166. }
  167. },
  168. methods: {
  169. /**
  170. * 检查type是否符合types的mime
  171. * @param type 文件后缀
  172. * @param types 可用文件后缀集合
  173. */
  174. handleCheckFileMime(type, types) {
  175. const typeMimes = types.map(item => mime.getType(item))
  176. return typeMimes.includes(type)
  177. },
  178. handleCheckFileSize(fileSize, limitSize) {
  179. const limitByteSize = limitSize * 1024 * 1024
  180. return limitByteSize > fileSize
  181. },
  182. /**
  183. * 上传之前的钩子
  184. * @param file
  185. * @return {undefined}
  186. */
  187. handleBeforeUpload(file) {
  188. // 检查mime
  189. const fileType = file.type || mime.getType(file.name.slice(file.name.lastIndexOf('.') + 1))
  190. const checkFileMime = this.handleCheckFileMime(fileType, this.types)
  191. const checkFileSize = this.handleCheckFileSize(file.size, this.limitSize);
  192. !checkFileSize ? file.isJumpRemove = true : undefined
  193. !checkFileSize ? this.$notify.warning(`文件大小不得超出${ this.limitSize }m`) : undefined
  194. !checkFileMime ? file.isJumpRemove = true : undefined
  195. !checkFileMime ? this.$notify.warning(`文件类型不在合法列表 ${ this.types }`) : undefined
  196. if(checkFileSize && checkFileMime){
  197. // 开启loading
  198. this.isUploading = true
  199. if(this.needFullScreenLoading){
  200. fullLoading = this.$loading({
  201. background:`rgba(255,255,255,0.5)`,
  202. text:'上传中',
  203. fullscreen:true
  204. })
  205. }
  206. }
  207. return checkFileSize && checkFileMime
  208. },
  209. /**
  210. * 上传成功钩子
  211. * @param response
  212. * @param file
  213. * @param fileList
  214. */
  215. handleUploadSuccess(response, file, fileList) {
  216. this.isUploading = false
  217. if(this.needFullScreenLoading){
  218. fullLoading?.close()
  219. }
  220. const successObj = {
  221. url: response.data[this.responseFileName],
  222. name: file.name
  223. }
  224. file.url = response.data[this.responseFileName]
  225. this.fileList.push(successObj)
  226. },
  227. /**
  228. * 上传失败的钩子
  229. * @param err
  230. * @param file
  231. * @param fileList
  232. */
  233. handleUploadError(err, file, fileList) {
  234. },
  235. /**
  236. * 超出数量的钩子
  237. * @param files
  238. * @param fileList
  239. */
  240. handleExceed(files, fileList) {
  241. this.$notify.warning(`文件总数大于可上传数量 ${ this.limit }`)
  242. },
  243. /**
  244. * 文件即将移除的钩子
  245. * @param file
  246. * @param fileList
  247. */
  248. async handleBeforeRemove(file, fileList) {
  249. // 如果是超出文件大小调用,放行
  250. if (file?.raw?.isJumpRemove) {
  251. return true
  252. }
  253. return await this.$confirm('此操作将会删除已上传的文件, 是否继续?', '提示', {
  254. confirmButtonText: '确定',
  255. cancelButtonText: '取消',
  256. type: 'warning'
  257. })
  258. },
  259. /**
  260. * 移除文件的钩子
  261. */
  262. handleRemove(file, fileList) {
  263. if (file?.raw?.isJumpRemove) {
  264. return
  265. }
  266. this.fileList.splice(this.fileList.findIndex(fileItem => file?.response?.data[this.responseFileName] === fileItem.url || file.url === fileItem.url), 1)
  267. },
  268. /**
  269. * 通知父组件
  270. */
  271. handleNotifyFather(){
  272. this.$emit('change',this.fileList)
  273. },
  274. /**
  275. * 预览
  276. * 图片视频直接预览,其他下载
  277. * @param file
  278. */
  279. handlePreviewOpen(file){
  280. if(!file.type){
  281. file.type = mime.getType(file?.url?.slice(file?.url?.lastIndexOf('.')+1)) || mime.getType(file?.name?.slice(file?.name.lastIndexOf('.')+1)) || undefined
  282. }
  283. if(file.type.includes('image') || file.type.includes('video')){
  284. this.previewObj.file = file
  285. this.previewObj.show = true
  286. }else{
  287. this.$confirm('需要下载才能预览此文件, 是否继续?', '提示', {
  288. confirmButtonText: '确定',
  289. cancelButtonText: '取消',
  290. type: 'warning'
  291. }).then(() => {
  292. let htmlAnchorElement = document.createElement('a');
  293. htmlAnchorElement.download = file?.url.slice(file?.url.lastIndexOf('/')+1)
  294. htmlAnchorElement.target='_bank'
  295. htmlAnchorElement.href = file?.url
  296. htmlAnchorElement.click()
  297. htmlAnchorElement = null
  298. }).catch(() => {
  299. });
  300. }
  301. },
  302. handlePreviewClose(){
  303. this.previewObj.file = null
  304. this.previewObj.show = false
  305. }
  306. }
  307. }
  308. </script>
  309. <style
  310. lang="scss"
  311. scoped
  312. >
  313. ::v-deep .el-upload {
  314. width: 100% !important;
  315. .el-upload-dragger {
  316. width: 100% !important;
  317. }
  318. }
  319. .error-text {
  320. font-size: 18px;
  321. font-weight: bolder;
  322. color: red;
  323. animation: error-animation 2.5s ease-in-out infinite;
  324. }
  325. @keyframes error-animation {
  326. 0%, 100% {
  327. font-size: 18px;
  328. color: red;
  329. }
  330. 25%, 75% {
  331. font-size: 16px;
  332. color: #b9b1b1;
  333. }
  334. 50% {
  335. font-size: 18px;
  336. color: #500000;
  337. }
  338. }
  339. .preview-content{
  340. display: flex;
  341. align-items: center;
  342. justify-content: center;
  343. .preview-item{
  344. min-width: 800px;
  345. }
  346. }
  347. </style>