multiple.vue 8.1 KB

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