multiple.vue 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. <template>
  2. <div class="sc-upload-multiple">
  3. <el-upload ref="uploader" list-type="picture-card"
  4. v-model:file-list="defaultFileList"
  5. action=""
  6. accept="image/gif, image/jpeg, image/png, video/mp4 , video/avi"
  7. :limit="limit"
  8. :multiple="multiple"
  9. :disabled="disabled"
  10. show-file-list
  11. :http-request="request"
  12. :before-upload="before"
  13. :on-success="success"
  14. :on-error="error"
  15. :on-exceed="handleExceed">
  16. <slot>
  17. <el-icon><el-icon-plus /></el-icon>
  18. </slot>
  19. <template #tip>
  20. <div v-if="tip" class="el-upload__tip">{{ tip }}</div>
  21. </template>
  22. <template #file="{ file }">
  23. <div class="sc-upload-list-item">
  24. <el-image v-if="isImage(file.mineType)" class="el-upload-list__item-thumbnail" :src="'/zcxt/folder/' + file.path" :preview-src-list="preview" fit="cover" preview-teleported :z-index="9999">
  25. <template #placeholder>
  26. <div class="sc-upload-multiple-image-slot">Loading...</div>
  27. </template>
  28. </el-image>
  29. <sc-video v-if="isVideo(file.mineType)" :src="'/zcxt/folder/' + file.path" showMask @play="videoPlay(file)"></sc-video>
  30. <div v-if="!disabled && file.status == 'success'" class="sc-upload__item-actions">
  31. <span class="del" @click="handleRemove(file)"><el-icon><el-icon-delete /></el-icon></span>
  32. </div>
  33. <div v-if="file.status == 'ready' || file.status == 'uploading'" class="sc-upload__item-progress">
  34. <el-progress :percentage="file.percentage" text-inside :stroke-width="16" />
  35. </div>
  36. </div>
  37. </template>
  38. </el-upload>
  39. <span style="display:none!important"><el-input v-model="value"></el-input></span>
  40. </div>
  41. <sc-video-viewer v-if="showVideoViewer" :videoUrl="previewVideoUrl" hideOnModal @close="showVideoViewer = false"></sc-video-viewer>
  42. </template>
  43. <script>
  44. import config from "@/config/upload";
  45. export default {
  46. props: {
  47. modelValue: { type: Array, default: () => [] },
  48. tip: { type: String, default: "" },
  49. maxSize: { type: Number, default: 50 },
  50. limit: { type: Number, default: 0 },
  51. multiple: { type: Boolean, default: true },
  52. disabled: { type: Boolean, default: false },
  53. onSuccess: { type: Function, default: () => { return true } }
  54. },
  55. data() {
  56. return {
  57. value: "",
  58. defaultFileList: [],
  59. showVideoViewer: false,
  60. previewVideoUrl: ""
  61. }
  62. },
  63. watch: {
  64. modelValue(val) {
  65. if (JSON.stringify(val) != JSON.stringify(this.formatArr(this.defaultFileList))) {
  66. this.defaultFileList = val;
  67. this.value = val;
  68. }
  69. },
  70. defaultFileList: {
  71. deep: true,
  72. handler(val) {
  73. this.$emit("update:modelValue", this.formatArr(val));
  74. this.value = val.map(v => v.path).join(",");
  75. }
  76. }
  77. },
  78. computed: {
  79. preview() {
  80. return this.defaultFileList.map(v => "/zcxt/folder/" + v.path);
  81. }
  82. },
  83. mounted() {
  84. this.defaultFileList = this.modelValue;
  85. this.value = this.modelValue;
  86. },
  87. methods: {
  88. isImage(type) {
  89. return config.imageIncludes(type);
  90. },
  91. isVideo(type) {
  92. return config.videoIncludes(type);
  93. },
  94. // 格式化数组值
  95. formatArr(arr) {
  96. return arr.map(item => ({ id: item.id, name: item.name, mineType: item.mineType, path: item.path }));
  97. },
  98. before(file) {
  99. if (!this.isImage(file.type) && !this.isVideo(file.type)) {
  100. this.$message.warning({ title: "上传文件警告", message: "选择的文件非图像类/视频类文件" });
  101. return false;
  102. }
  103. const maxSize = file.size / 1024 / 1024 < this.maxSize;
  104. if (!maxSize) {
  105. this.$message.warning(`上传文件大小不能超过 ${this.maxSize}MB!`);
  106. return false;
  107. }
  108. },
  109. success(res, file) {
  110. let os = this.onSuccess(res, file);
  111. if (os != undefined && os == false) return false;
  112. file.name = res.fileName;
  113. file.path = res.path;
  114. file.mineType = res.mineType;
  115. },
  116. error(message) {
  117. this.$notify.error({ title: "上传文件未成功", message });
  118. },
  119. beforeRemove({ id, name }) {
  120. return this.$confirm(`是否移除 ${name}? 此操作不可逆!`, "提示", {
  121. type: "warning",
  122. confirmButtonText: "移除"
  123. }).then(() => {
  124. if (id) {
  125. this.$API.common.folder.rm(id).then(res => {
  126. if (res.code == 200) return true;
  127. else return false;
  128. }).catch(() => {
  129. return false;
  130. });
  131. } return true;
  132. }).catch(() => {
  133. return false;
  134. });
  135. },
  136. handleRemove(file) {
  137. this.$refs.uploader.handleRemove(file);
  138. },
  139. handleExceed() {
  140. this.$message.warning(`当前设置最多上传 ${this.limit} 个文件,请移除后上传!`);
  141. },
  142. request(param) {
  143. const data = new FormData();
  144. data.append(param.filename, param.file);
  145. this.$API.common.folder.up(data, {
  146. onUploadProgress: e => {
  147. const percent = parseInt(((e.loaded / e.total) * 100) | 0, 10)
  148. param.onProgress({ percent });
  149. }
  150. }).then(res => {
  151. if (res.code == 200) param.onSuccess({ path: res.expands.file, fileName: param.file.name, mineType: param.file.type })
  152. else param.onError(res.message || "未知错误");
  153. }).catch(err => param.onError(err));
  154. },
  155. videoPlay(file) {
  156. this.showVideoViewer = true;
  157. this.previewVideoUrl = "/zcxt/folder/" + file.path;
  158. }
  159. }
  160. }
  161. </script>
  162. <style scoped>
  163. .el-form-item.is-error .sc-upload-multiple:deep(.el-upload--picture-card) {
  164. border-color: var(--el-color-danger);
  165. }
  166. :deep(.el-upload-list__item) {
  167. transition: none;
  168. border-radius: 0;
  169. }
  170. .sc-upload-multiple:deep(.el-upload-list__item.el-list-leave-active) {
  171. position: static !important;
  172. }
  173. .sc-upload-multiple:deep(.el-upload--picture-card) {
  174. border-radius: 0;
  175. }
  176. .sc-upload-list-item {
  177. width: 100%;
  178. height: 100%;
  179. position: relative;
  180. }
  181. .sc-upload-multiple .el-image {
  182. display: block;
  183. }
  184. .sc-upload-multiple .el-image:deep(img) {
  185. -webkit-user-drag: none;
  186. }
  187. .sc-upload-multiple-image-slot {
  188. display: flex;
  189. justify-content: center;
  190. align-items: center;
  191. width: 100%;
  192. height: 100%;
  193. font-size: 12px;
  194. }
  195. .sc-upload-multiple .el-upload-list__item:hover .sc-upload__item-actions {
  196. display: block;
  197. }
  198. .sc-upload__item-actions {
  199. position: absolute;
  200. top: 0;
  201. right: 0;
  202. display: none;
  203. }
  204. .sc-upload__item-actions span {
  205. display: flex;
  206. justify-content: center;
  207. align-items: center;
  208. width: 25px;
  209. height: 25px;
  210. cursor: pointer;
  211. color: #fff;
  212. }
  213. .sc-upload__item-actions span i {
  214. font-size: 12px;
  215. }
  216. .sc-upload__item-actions .del {
  217. background: #f56c6c;
  218. }
  219. .sc-upload__item-progress {
  220. position: absolute;
  221. width: 100%;
  222. height: 100%;
  223. top: 0;
  224. left: 0;
  225. background-color: var(--el-overlay-color-lighter);
  226. }
  227. </style>