图片拖拽排序 - 图1

安装拖拽插件

  1. npm install vuedraggable

main.js 引入插件

  1. // 图片拖动
  2. import vuedraggable from 'vuedraggable'
  3. // 全局组件挂载
  4. Vue.component('vuedraggable', vuedraggable)

如何使用

  1. <imgUpload v-model="imgList" :limit="20" @input="handleProductImg" />
  2. <script>
  3. import imgUpload from '@/components/upload/imgUpload'
  4. export default {
  5. name: "imgTest",
  6. components: {imgUpload},
  7. data() {
  8. return {
  9. imgList:[],
  10. imgs:""
  11. };
  12. },
  13. methods: {
  14. handleProductImg(imgUrl) {
  15. this.imgList = imgUrl;
  16. // 逗号拼接
  17. this.imgs = imgUrl.join(",");
  18. },
  19. }
  20. }
  21. </script>
  • 组件代码 imgUpload.vue,组件路径 manage-ui/src/components/upload/imgUpload.vue
  1. <template>
  2. <div class="uploadWrapper">
  3. <vuedraggable
  4. :class="{ single: isSingle, maxHidden: isMaxHidden }"
  5. @end="onDragEnd"
  6. @start="onDragStart"
  7. class="vue-draggable"
  8. draggable=".draggable-item"
  9. tag="ul"
  10. v-model="imgList"
  11. >
  12. <!-- 拖拽元素 -->
  13. <li
  14. :key="item + index"
  15. :style="{ width: width + 'px', height: height + 'px' }"
  16. class="draggable-item"
  17. v-for="(item, index) in imgList"
  18. >
  19. <el-image :preview-src-list="imgList" :src="item"></el-image>
  20. <div @click="onRemoveHandler(index)" class="shadow">
  21. <i class="el-icon-delete"></i>
  22. </div>
  23. </li>
  24. <!-- 上传按钮 -->
  25. <el-upload
  26. :action="url"
  27. :before-upload="beforeUpload"
  28. :headers="headers"
  29. :limit="limit"
  30. :multiple="!isSingle"
  31. :on-exceed="onExceed"
  32. :on-success="onSuccessUpload"
  33. :show-file-list="false"
  34. :style="{ width: width + 'px', height: height + 'px' }"
  35. accept=".jpg,.jpeg,.png,.gif"
  36. class="uploadBox"
  37. ref="uploadRef"
  38. slot="footer"
  39. >
  40. <i class="el-icon-plus uploadIcon">
  41. <span class="uploading" v-show="isUploading">正在上传...</span>
  42. <span
  43. class="limitTxt"
  44. v-if="!isUploading && limit && limit!==99 && !isSingle"
  45. >最多{{ limit }}张</span>
  46. </i>
  47. </el-upload>
  48. </vuedraggable>
  49. </div>
  50. </template>
  51. <script>
  52. import vuedraggable from 'vuedraggable'
  53. // 获取token,用于后端接口登录校验,根据公司的业务自行移除或替换就行
  54. import {getToken} from "@/utils/auth"
  55. // import lrz from 'lrz' // 前端图片压缩插件
  56. import tools from '@/utils/tools'
  57. export default {
  58. name: 'ImgUpload',
  59. props: {
  60. // 图片数据(图片url组成的数组) 通过v-model传递
  61. value: {
  62. type: Array,
  63. default() {
  64. return []
  65. }
  66. },
  67. // 限制上传的图片数量
  68. limit: {
  69. type: Number,
  70. default: 8
  71. },
  72. // 限制上传图片的文件大小(kb)
  73. size: {
  74. type: Number,
  75. default: 500
  76. },
  77. // 是否是单图上传(单图上传就是已传图片和上传按钮重叠)
  78. isSingle: {
  79. type: Boolean,
  80. default: false
  81. },
  82. // 是否使用图片压缩
  83. useCompress: {
  84. type: Boolean,
  85. default: false
  86. },
  87. // 图片显示的宽度(px)
  88. width: {
  89. type: Number,
  90. default: 100
  91. },
  92. // 图片显示的高度(px)
  93. height: {
  94. type: Number,
  95. default: 100
  96. }
  97. },
  98. data() {
  99. return {
  100. url: process.env.VUE_APP_BASE_API + "/common/upload",
  101. headers: {Authorization: 'Bearer ' + getToken()},
  102. isUploading: false, // 正在上传状态
  103. isFirstMount: true // 控制防止重复回显
  104. }
  105. },
  106. computed: {
  107. // 图片数组数据
  108. imgList: {
  109. get() {
  110. return this.value
  111. },
  112. set(val) {
  113. if (val.length < this.imgList.length) {
  114. // 判断是删除图片时同步el-upload数据
  115. this.syncElUpload(val)
  116. }
  117. // 同步v-model
  118. this.$emit('input', val)
  119. }
  120. },
  121. // 控制达到最大限制时隐藏上传按钮
  122. isMaxHidden() {
  123. return this.imgList.length >= this.limit
  124. }
  125. },
  126. watch: {
  127. value: {
  128. handler(val) {
  129. if (this.isFirstMount && this.value.length > 0) {
  130. this.syncElUpload()
  131. }
  132. },
  133. deep: true
  134. }
  135. },
  136. mounted() {
  137. if (this.value.length > 0) {
  138. this.syncElUpload()
  139. }
  140. },
  141. methods: {
  142. // 同步el-upload数据
  143. syncElUpload(val) {
  144. const imgList = val || this.imgList
  145. this.$refs.uploadRef.uploadFiles = imgList.map((v, i) => {
  146. return {
  147. name: 'pic' + i,
  148. url: v,
  149. status: 'success',
  150. uid: tools.createUniqueString()
  151. }
  152. })
  153. this.isFirstMount = false
  154. },
  155. // 上传图片之前
  156. beforeUpload(file) {
  157. this.isFirstMount = false
  158. // if (this.useCompress) {
  159. // // 图片压缩
  160. // return new Promise((resolve, reject) => {
  161. // lrz(file, { width: 1920 }).then((rst) => {
  162. // file = rst.file
  163. // }).always(() => {
  164. // if (validImgUpload(file, this.size)) {
  165. // this.isUploading = true
  166. // resolve()
  167. // } else {
  168. // reject(new Error())
  169. // }
  170. // })
  171. // })
  172. // } else {
  173. // if (validImgUpload(file, this.size)) {
  174. // this.isUploading = true
  175. // return true
  176. // } else {
  177. // return false
  178. // }
  179. // }
  180. },
  181. // 上传完单张图片
  182. onSuccessUpload(res, file, fileList) {
  183. // 这里需要根据你自己的接口返回数据格式和层级来自行修改
  184. if (res.url) {
  185. // 判断接口上传成功
  186. if (this.imgList.length < this.limit) {
  187. // 未超限时,把接口返回的图片url地址添加到imgList
  188. this.imgList.push(res.url)
  189. this.$emit('input', this.imgList)
  190. }
  191. } else {
  192. // 判断接口上传失败
  193. this.syncElUpload()
  194. this.$message({type: 'error', message: res.msg})
  195. }
  196. this.isUploading = false
  197. },
  198. // 移除单张图片
  199. onRemoveHandler(index) {
  200. this.$confirm('确定删除该图片?', '提示', {
  201. confirmButtonText: '确定',
  202. cancelButtonText: '取消',
  203. type: 'warning'
  204. })
  205. .then(() => {
  206. this.imgList = this.imgList.filter((v, i) => {
  207. return i !== index
  208. })
  209. })
  210. .catch(() => {
  211. })
  212. },
  213. // 超限
  214. onExceed() {
  215. this.$refs.uploadRef.abort() // 取消剩余接口请求
  216. this.syncElUpload()
  217. this.$message({
  218. type: 'warning',
  219. message: `图片超限,最多可上传${this.limit}张图片`
  220. })
  221. },
  222. onDragStart(e) {
  223. e.target.classList.add('hideShadow')
  224. },
  225. onDragEnd(e) {
  226. e.target.classList.remove('hideShadow')
  227. }
  228. },
  229. components: {vuedraggable}
  230. }
  231. </script>
  232. <style lang="less" scoped>
  233. /deep/ .el-upload {
  234. width: 100%;
  235. height: 100%;
  236. }
  237. // 上传按钮
  238. .uploadIcon {
  239. width: 100%;
  240. height: 100%;
  241. position: relative;
  242. display: flex;
  243. align-items: center;
  244. justify-content: center;
  245. border: 1px dashed #c0ccda;
  246. background-color: #fbfdff;
  247. border-radius: 6px;
  248. font-size: 20px;
  249. color: #999;
  250. .limitTxt,
  251. .uploading {
  252. position: absolute;
  253. bottom: 10%;
  254. left: 0;
  255. width: 100%;
  256. font-size: 14px;
  257. text-align: center;
  258. }
  259. }
  260. // 拖拽
  261. .vue-draggable {
  262. display: flex;
  263. flex-wrap: wrap;
  264. .draggable-item {
  265. margin-right: 5px;
  266. margin-bottom: 5px;
  267. border: 1px solid #ddd;
  268. border-radius: 6px;
  269. position: relative;
  270. overflow: hidden;
  271. .el-image {
  272. width: 100%;
  273. height: 100%;
  274. }
  275. .shadow {
  276. position: absolute;
  277. top: 0;
  278. right: 0;
  279. background-color: rgba(0, 0, 0, .5);
  280. opacity: 0;
  281. transition: opacity .3s;
  282. color: #fff;
  283. font-size: 20px;
  284. line-height: 20px;
  285. padding: 2px;
  286. cursor: pointer;
  287. }
  288. &:hover {
  289. .shadow {
  290. opacity: 1;
  291. }
  292. }
  293. }
  294. &.hideShadow {
  295. .shadow {
  296. display: none;
  297. }
  298. }
  299. &.single {
  300. overflow: hidden;
  301. position: relative;
  302. .draggable-item {
  303. position: absolute;
  304. left: 0;
  305. top: 0;
  306. z-index: 1;
  307. }
  308. }
  309. &.maxHidden {
  310. .uploadBox {
  311. display: none;
  312. }
  313. }
  314. }
  315. // el-image
  316. .el-image-viewer__wrapper {
  317. .el-image-viewer__mask {
  318. opacity: .8;
  319. }
  320. .el-icon-circle-close {
  321. color: #fff;
  322. }
  323. }
  324. </style>

参考

https://blog.csdn.net/u010059669/article/details/104038160
https://www.it610.com/article/1295907878112141312.htm