多租户商城-商户端
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

372 lines
9.4 KiB

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