imageUpload.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393
  1. <template>
  2. <view class="imageUploadContainer">
  3. <view class="imageUploadList">
  4. <view class="imageItem" v-bind:key="index" v-for="(path, index) in imageListData">
  5. <image
  6. :src="path"
  7. :class="{ dragging: isDragging(index) }"
  8. draggable="true"
  9. @tap="previewImage"
  10. :data-index="index"
  11. @touchstart="start"
  12. @touchmove.stop.prevent="move"
  13. @touchend="stop"
  14. ></image>
  15. <view v-if="isShowDel" class="imageDel" @tap="deleteImage" :data-index="index">x</view>
  16. </view>
  17. <view v-if="isShowAdd" class="imageUpload" @tap="selectImage">+</view>
  18. </view>
  19. <image v-if="showMoveImage" class="moveImage" :style="{ left: posMoveImageLeft, top: posMoveImageTop }" :src="moveImagePath"></image>
  20. </view>
  21. </template>
  22. <script>
  23. export default {
  24. name: "robby-image-upload",
  25. props: ["value", "enableDel", "enableAdd", "enableDrag", "serverUrl", "formData", "header", "limit", "fileKeyName", "showUploadProgress", "serverUrlDeleteImage"],
  26. data() {
  27. return {
  28. imageBasePos: {
  29. x0: -1,
  30. y0: -1,
  31. w: -1,
  32. h: -1,
  33. },
  34. showMoveImage: false,
  35. moveImagePath: "",
  36. moveLeft: 0,
  37. moveTop: 0,
  38. deltaLeft: 0,
  39. deltaTop: 0,
  40. dragIndex: null,
  41. targetImageIndex: null,
  42. imageList: [],
  43. isDestroyed: false,
  44. };
  45. },
  46. mounted: function () {
  47. this.imageList = this.value;
  48. if (this.showUploadProgress === false) {
  49. this.showUploadProgress = false;
  50. } else {
  51. this.showUploadProgress = true;
  52. }
  53. },
  54. destroyed: function () {
  55. this.isDestroyed = true;
  56. },
  57. computed: {
  58. imageListData: function () {
  59. if (this.value) {
  60. return this.value;
  61. }
  62. },
  63. posMoveImageLeft: function () {
  64. return this.moveLeft + "px";
  65. },
  66. posMoveImageTop: function () {
  67. return this.moveTop + "px";
  68. },
  69. isShowDel: function () {
  70. if (this.enableDel === false) {
  71. return false;
  72. } else {
  73. return true;
  74. }
  75. },
  76. isShowAdd: function () {
  77. if (this.enableAdd === false) {
  78. return false;
  79. }
  80. if (this.limit && this.imageList.length >= this.limit) {
  81. return false;
  82. }
  83. return true;
  84. },
  85. isDragable: function () {
  86. if (this.enableDrag === false) {
  87. return false;
  88. } else {
  89. return true;
  90. }
  91. },
  92. },
  93. methods: {
  94. selectImage: function () {
  95. var _self = this;
  96. if (!_self.imageList) {
  97. _self.imageList = [];
  98. }
  99. uni.chooseImage({
  100. count: _self.limit ? _self.limit - _self.imageList.length : 999,
  101. success: function (e) {
  102. var imagePathArr = e.tempFilePaths;
  103. //如果设置了limit限制,在web上count参数无效,这里做判断控制选择的数量是否合要求
  104. //在非微信小程序里,虽然可以选多张,但选择的结果会被截掉
  105. //在app里,会自动做选择数量的限制
  106. if (_self.limit) {
  107. var availableImageNumber = _self.limit - _self.imageList.length;
  108. if (availableImageNumber < imagePathArr.length) {
  109. uni.showToast({
  110. title: "图片总数限制为" + _self.limit + "张,当前还可以选" + availableImageNumber + "张",
  111. icon: "none",
  112. mask: false,
  113. duration: 2000,
  114. });
  115. return;
  116. }
  117. }
  118. //检查服务器地址是否设置,设置即表示图片要上传到服务器
  119. if (_self.serverUrl) {
  120. uni.showToast({
  121. title: "上传进度:0/" + imagePathArr.length,
  122. icon: "none",
  123. mask: false,
  124. });
  125. var remoteIndexStart = _self.imageList.length - imagePathArr.length;
  126. var promiseWorkList = [];
  127. var keyname = _self.fileKeyName ? _self.fileKeyName : "upload-images";
  128. var completeImages = 0;
  129. for (let i = 0; i < imagePathArr.length; i++) {
  130. promiseWorkList.push(
  131. new Promise((resolve, reject) => {
  132. let remoteUrlIndex = remoteIndexStart + i;
  133. uni.uploadFile({
  134. url: _self.serverUrl,
  135. fileType: "image",
  136. header: _self.header,
  137. formData: _self.formData,
  138. filePath: imagePathArr[i],
  139. name: keyname,
  140. success: function (res) {
  141. if (res.statusCode === 200) {
  142. if (_self.isDestroyed) {
  143. return;
  144. }
  145. completeImages++;
  146. if (_self.showUploadProgress) {
  147. uni.showToast({
  148. title: "上传进度:" + completeImages + "/" + imagePathArr.length,
  149. icon: "none",
  150. mask: false,
  151. duration: 500,
  152. });
  153. }
  154. console.log("success to upload image: " + res.data);
  155. resolve(res.data);
  156. } else {
  157. console.log("fail to upload image:" + res.data);
  158. reject("fail to upload image:" + remoteUrlIndex);
  159. }
  160. },
  161. fail: function (res) {
  162. console.log("fail to upload image:" + res);
  163. reject("fail to upload image:" + remoteUrlIndex);
  164. },
  165. });
  166. })
  167. );
  168. }
  169. Promise.all(promiseWorkList).then((result) => {
  170. if (_self.isDestroyed) {
  171. return;
  172. }
  173. for (let i = 0; i < result.length; i++) {
  174. _self.imageList.push(result[i]);
  175. }
  176. _self.$emit("add", {
  177. currentImages: imagePathArr,
  178. allImages: _self.imageList,
  179. });
  180. _self.$emit("input", _self.imageList);
  181. });
  182. } else {
  183. for (let i = 0; i < imagePathArr.length; i++) {
  184. _self.imageList.push(imagePathArr[i]);
  185. }
  186. _self.$emit("add", {
  187. currentImages: imagePathArr,
  188. allImages: _self.imageList,
  189. });
  190. _self.$emit("input", _self.imageList);
  191. }
  192. },
  193. });
  194. },
  195. deleteImage: function (e) {
  196. var imageIndex = e.currentTarget.dataset.index;
  197. var deletedImagePath = this.imageList[imageIndex];
  198. this.imageList.splice(imageIndex, 1);
  199. //检查删除图片的服务器地址是否设置,如果设置则调用API,在服务器端删除该图片
  200. if (this.serverUrlDeleteImage) {
  201. uni.request({
  202. url: this.serverUrlDeleteImage,
  203. method: "GET",
  204. data: {
  205. imagePath: deletedImagePath,
  206. },
  207. success: (res) => {
  208. console.log(res.data);
  209. },
  210. });
  211. }
  212. this.$emit("delete", {
  213. currentImage: deletedImagePath,
  214. allImages: this.imageList,
  215. });
  216. this.$emit("input", this.imageList);
  217. },
  218. previewImage: function (e) {
  219. var imageIndex = e.currentTarget.dataset.index;
  220. uni.previewImage({
  221. current: this.imageList[imageIndex],
  222. indicator: "number",
  223. loop: "true",
  224. urls: this.imageList,
  225. });
  226. },
  227. initImageBasePos: function () {
  228. let paddingRate = 0.024;
  229. var _self = this;
  230. //计算图片基准位置
  231. uni.getSystemInfo({
  232. success: function (obj) {
  233. let screenWidth = obj.screenWidth;
  234. let leftPadding = Math.ceil(paddingRate * screenWidth);
  235. let imageWidth = Math.ceil((screenWidth - 2 * leftPadding) / 4);
  236. _self.imageBasePos.x0 = leftPadding;
  237. _self.imageBasePos.w = imageWidth;
  238. _self.imageBasePos.h = imageWidth;
  239. },
  240. });
  241. },
  242. findOverlapImage: function (posX, posY) {
  243. let rows = Math.floor((posX - this.imageBasePos.x0) / this.imageBasePos.w);
  244. let cols = Math.floor((posY - this.imageBasePos.y0) / this.imageBasePos.h);
  245. let indx = cols * 4 + rows;
  246. return indx;
  247. },
  248. isDragging: function (indx) {
  249. return this.dragIndex === indx;
  250. },
  251. start: function (e) {
  252. console.log(this.isDragable);
  253. if (!this.isDragable) {
  254. return;
  255. }
  256. this.dragIndex = e.currentTarget.dataset.index;
  257. this.moveImagePath = this.imageList[this.dragIndex];
  258. this.showMoveImage = true;
  259. //计算纵向图片基准位置
  260. if (this.imageBasePos.y0 === -1) {
  261. this.initImageBasePos();
  262. let basePosY = Math.floor(this.dragIndex / 4) * this.imageBasePos.h;
  263. let currentImageOffsetTop = e.currentTarget.offsetTop;
  264. this.imageBasePos.y0 = currentImageOffsetTop - basePosY;
  265. }
  266. //设置选中图片当前左上角的坐标
  267. this.moveLeft = e.target.offsetLeft;
  268. this.moveTop = e.target.offsetTop;
  269. },
  270. move: function (e) {
  271. if (!this.isDragable) {
  272. return;
  273. }
  274. const touch = e.touches[0];
  275. this.targetImageIndex = this.findOverlapImage(touch.clientX, touch.clientY);
  276. //初始化deltaLeft/deltaTop
  277. if (this.deltaLeft === 0) {
  278. this.deltaLeft = touch.clientX - this.moveLeft;
  279. this.deltaTop = touch.clientY - this.moveTop;
  280. }
  281. //设置移动图片位置
  282. this.moveLeft = touch.clientX - this.deltaLeft;
  283. this.moveTop = touch.clientY - this.deltaTop;
  284. },
  285. stop: function (e) {
  286. if (!this.isDragable) {
  287. return;
  288. }
  289. if (this.dragIndex !== null && this.targetImageIndex !== null) {
  290. if (this.targetImageIndex < 0) {
  291. this.targetImageIndex = 0;
  292. }
  293. if (this.targetImageIndex >= this.imageList.length) {
  294. this.targetImageIndex = this.imageList.length - 1;
  295. }
  296. //交换图片
  297. if (this.dragIndex !== this.targetImageIndex) {
  298. this.imageList[this.dragIndex] = this.imageList[this.targetImageIndex];
  299. this.imageList[this.targetImageIndex] = this.moveImagePath;
  300. }
  301. }
  302. this.dragIndex = null;
  303. this.targetImageIndex = null;
  304. this.deltaLeft = 0;
  305. this.deltaTop = 0;
  306. this.showMoveImage = false;
  307. this.$emit("input", this.imageList);
  308. },
  309. },
  310. };
  311. </script>
  312. <style>
  313. .imageUploadContainer {
  314. /* padding: 10upx 5upx;
  315. margin: 10upx 5upx; */
  316. }
  317. .dragging {
  318. transform: scale(1.2);
  319. }
  320. .imageUploadList {
  321. display: flex;
  322. flex-wrap: wrap;
  323. }
  324. .imageItem,
  325. .imageUpload {
  326. width: 160upx;
  327. height: 160upx;
  328. margin: 20upx 16upx 20upx 0;
  329. }
  330. .imageDel {
  331. position: relative;
  332. left: 120upx;
  333. bottom: 165upx;
  334. background-color: rgba(0, 0, 0, 0.5);
  335. width: 36upx;
  336. text-align: center;
  337. line-height: 35upx;
  338. border-radius: 17upx;
  339. color: white;
  340. font-size: 30upx;
  341. padding-bottom: 2upx;
  342. }
  343. .imageItem image,
  344. .moveImage {
  345. width: 160upx;
  346. height: 160upx;
  347. border-radius: 8upx;
  348. }
  349. .imageUpload {
  350. line-height: 130upx;
  351. text-align: center;
  352. font-size: 150upx;
  353. color: #d9d9d9;
  354. border: 1px solid #d9d9d9;
  355. border-radius: 8upx;
  356. }
  357. .moveImage {
  358. position: absolute;
  359. }
  360. </style>