shmily-drag-image.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  1. <template>
  2. <!-- 多图上传支持拖拽排序 -->
  3. <view class="con">
  4. <movable-area class="area" :style="{ height: areaHeight }" @mouseenter="mouseenter" @mouseleave="mouseleave">
  5. <block v-for="(item, index) in imageList" :key="item.id">
  6. <movable-view class="view" :x="item.x" :y="item.y" direction="all" :damping="40" :disabled="item.disable" @change="onChange($event, item)"
  7. @touchstart="touchstart(item)" @mousedown="touchstart(item)" @touchend="touchend(item)" @mouseup="touchend(item)"
  8. :style="{ width: viewWidth + 'px', height: viewWidth + 'px', 'z-index': item.zIndex, opacity: item.opacity }">
  9. <view class="area-con" :style="{ width: childWidth, height: childWidth, transform: 'scale(' + item.scale + ')' }">
  10. <image class="pre-image" :src="item.src" mode="aspectFill"></image>
  11. <view class="del-con" @click="delImage(item, index)" @touchstart.stop="delImageMp(item, index)" @touchend.stop="nothing()"
  12. @mousedown.stop="nothing()" @mouseup.stop="nothing()">
  13. <view class="del-wrap">
  14. <image class="del-image" src="/static/image/camera.png"></image>
  15. </view>
  16. </view>
  17. </view>
  18. </movable-view>
  19. </block>
  20. <view class="add" v-if="imageList.length < number" :style="{ top: add.y, left: add.x, width: viewWidth + 'px', height: viewWidth + 'px' }"
  21. @click="addImages">
  22. <view class="add-wrap" :style="{ width: childWidth, height: childWidth }">
  23. <image style="width: 192rpx;height: 192rpx;" src="/static/image/camera.png"></image>
  24. </view>
  25. </view>
  26. </movable-area>
  27. </view>
  28. </template>
  29. <script>
  30. import configdata from '../../common/config.js';
  31. export default {
  32. data() {
  33. return {
  34. imageList: [],
  35. width: 0,
  36. add: {
  37. x: 0,
  38. y: 0
  39. },
  40. colsValue: 0,
  41. viewWidth: 0,
  42. tempItem: null,
  43. timer: null,
  44. changeStatus: true,
  45. preStatus: true,
  46. }
  47. },
  48. props: {
  49. // 返回排序后图片
  50. list: {
  51. type: Array,
  52. default: function() {
  53. return []
  54. }
  55. },
  56. // 选择图片数量限制
  57. number: {
  58. type: Number,
  59. default: 5
  60. },
  61. // 图片父容器宽度(实际显示的图片宽度为 imageWidth / 1.1 ),单位 rpx
  62. imageWidth: {
  63. type: Number,
  64. default: 230
  65. },
  66. // 图片列数(cols > 0 则 imageWidth 无效)
  67. cols: {
  68. type: Number,
  69. default: 3
  70. },
  71. // 图片周围空白填充,单位 rpx
  72. padding: {
  73. type: Number,
  74. default: 10
  75. },
  76. // 拖动图片时放大倍数 [0, ∞)
  77. scale: {
  78. type: Number,
  79. default: 1.1
  80. },
  81. // 拖动图片时不透明度
  82. opacity: {
  83. type: Number,
  84. default: 0.7
  85. },
  86. // 自定义添加(需配合 @aaddImage 事件使用)
  87. custom: {
  88. type: Boolean,
  89. default: false
  90. }
  91. },
  92. computed: {
  93. areaHeight() {
  94. if (this.imageList.length < this.number) {
  95. return Math.ceil((this.imageList.length + 1) / this.colsValue) * this.viewWidth + 'px'
  96. } else {
  97. return Math.ceil(this.imageList.length / this.colsValue) * this.viewWidth + 'px'
  98. }
  99. },
  100. childWidth() {
  101. return this.viewWidth - this.rpx2px(this.padding) * 2 + 'px'
  102. },
  103. },
  104. created() {
  105. this.width = uni.getSystemInfoSync().windowWidth
  106. this.viewWidth = this.rpx2px(this.imageWidth)
  107. },
  108. mounted() {
  109. const query = uni.createSelectorQuery().in(this)
  110. query.select('.area').boundingClientRect(data => {
  111. this.colsValue = Math.floor(data.width / this.viewWidth)
  112. if (this.cols > 0) {
  113. this.colsValue = this.cols
  114. this.viewWidth = data.width / this.cols
  115. }
  116. for (let item of this.list) {
  117. this.addProperties(item)
  118. }
  119. })
  120. query.exec()
  121. },
  122. methods: {
  123. onChange(e, item) {
  124. if (!item) return
  125. item.oldX = e.detail.x
  126. item.oldY = e.detail.y
  127. if (e.detail.source === 'touch') {
  128. if (item.moveEnd) {
  129. item.offset = Math.sqrt(Math.pow(item.oldX - item.absX * this.viewWidth, 2) + Math.pow(item.oldY - item.absY *
  130. this.viewWidth, 2))
  131. }
  132. let x = Math.floor((e.detail.x + this.viewWidth / 2) / this.viewWidth)
  133. if (x >= this.colsValue) return
  134. let y = Math.floor((e.detail.y + this.viewWidth / 2) / this.viewWidth)
  135. let index = this.colsValue * y + x
  136. if (item.index != index && index < this.imageList.length) {
  137. this.changeStatus = false
  138. for (let obj of this.imageList) {
  139. if (item.index > index && obj.index >= index && obj.index < item.index) {
  140. this.change(obj, 1)
  141. } else if (item.index < index && obj.index <= index && obj.index > item.index) {
  142. this.change(obj, -1)
  143. } else if (obj.id != item.id) {
  144. obj.offset = 0
  145. obj.x = obj.oldX
  146. obj.y = obj.oldY
  147. setTimeout(() => {
  148. this.$nextTick(() => {
  149. obj.x = obj.absX * this.viewWidth
  150. obj.y = obj.absY * this.viewWidth
  151. })
  152. }, 0)
  153. }
  154. }
  155. item.index = index
  156. item.absX = x
  157. item.absY = y
  158. this.sortList()
  159. }
  160. }
  161. },
  162. change(obj, i) {
  163. obj.index += i
  164. obj.offset = 0
  165. obj.x = obj.oldX
  166. obj.y = obj.oldY
  167. obj.absX = obj.index % this.colsValue
  168. obj.absY = Math.floor(obj.index / this.colsValue)
  169. setTimeout(() => {
  170. this.$nextTick(() => {
  171. obj.x = obj.absX * this.viewWidth
  172. obj.y = obj.absY * this.viewWidth
  173. })
  174. }, 0)
  175. },
  176. touchstart(item) {
  177. this.imageList.forEach(v => {
  178. v.zIndex = v.index + 9
  179. })
  180. item.zIndex = 99
  181. item.moveEnd = true
  182. this.tempItem = item
  183. this.timer = setTimeout(() => {
  184. item.scale = this.scale
  185. item.opacity = this.opacity
  186. clearTimeout(this.timer)
  187. this.timer = null
  188. }, 200)
  189. },
  190. touchend(item) {
  191. this.previewImage(item)
  192. item.scale = 1
  193. item.opacity = 1
  194. item.x = item.oldX
  195. item.y = item.oldY
  196. item.offset = 0
  197. item.moveEnd = false
  198. setTimeout(() => {
  199. this.$nextTick(() => {
  200. item.x = item.absX * this.viewWidth
  201. item.y = item.absY * this.viewWidth
  202. this.tempItem = null
  203. this.changeStatus = true
  204. })
  205. }, 0)
  206. },
  207. previewImage(item) {
  208. if (this.timer && this.preStatus && this.changeStatus && item.offset < 28.28) {
  209. clearTimeout(this.timer)
  210. this.timer = null
  211. let src = this.list.findIndex(v => v === item.src)
  212. uni.previewImage({
  213. urls: this.list,
  214. current: src,
  215. success: () => {
  216. this.preStatus = false
  217. setTimeout(() => {
  218. this.preStatus = true
  219. }, 600)
  220. }
  221. })
  222. } else if (this.timer) {
  223. clearTimeout(this.timer)
  224. this.timer = null
  225. }
  226. },
  227. mouseenter() {
  228. //#ifdef H5
  229. this.imageList.forEach(v => {
  230. v.disable = false
  231. })
  232. //#endif
  233. },
  234. mouseleave() {
  235. //#ifdef H5
  236. if (this.tempItem) {
  237. this.imageList.forEach(v => {
  238. v.disable = true
  239. v.zIndex = v.index + 9
  240. v.offset = 0
  241. v.moveEnd = false
  242. if (v.id == this.tempItem.id) {
  243. if (this.timer) {
  244. clearTimeout(this.timer)
  245. this.timer = null
  246. }
  247. v.scale = 1
  248. v.opacity = 1
  249. v.x = v.oldX
  250. v.y = v.oldY
  251. this.$nextTick(() => {
  252. v.x = v.absX * this.viewWidth
  253. v.y = v.absY * this.viewWidth
  254. this.tempItem = null
  255. })
  256. }
  257. })
  258. this.changeStatus = true
  259. }
  260. //#endif
  261. },
  262. addImages() {
  263. let that = this
  264. if (this.custom) {
  265. this.$emit('addImage')
  266. } else {
  267. let checkNumber = this.number - this.imageList.length
  268. uni.chooseImage({
  269. count: checkNumber,
  270. sourceType: ['album', 'camera'],
  271. success: res => {
  272. let count = checkNumber <= res.tempFilePaths.length ? checkNumber : res.tempFilePaths.length
  273. for (let i = 0; i < count; i++) {
  274. this.$queue.showLoading("上传中...");
  275. uni.uploadFile({ // 上传接口
  276. url: that.config("APIHOST1") + '/alioss/upload', //真实的接口地址
  277. filePath: res.tempFilePaths[i],
  278. name: 'file',
  279. success: (uploadFileRes) => {
  280. // this.addProperties(JSON.parse(uploadFileRes.data).data.src) //列表接口
  281. this.addProperties(JSON.parse(uploadFileRes.data).data)
  282. uni.hideLoading();
  283. }
  284. });
  285. }
  286. }
  287. })
  288. }
  289. },
  290. config: function(name) {
  291. var info = null;
  292. if (name) {
  293. var name2 = name.split("."); //字符分割
  294. if (name2.length > 1) {
  295. info = configdata[name2[0]][name2[1]] || null;
  296. } else {
  297. info = configdata[name] || null;
  298. }
  299. if (info == null) {
  300. let web_config = cache.get("web_config");
  301. if (web_config) {
  302. if (name2.length > 1) {
  303. info = web_config[name2[0]][name2[1]] || null;
  304. } else {
  305. info = web_config[name] || null;
  306. }
  307. }
  308. }
  309. }
  310. return info;
  311. },
  312. addImage(image) {
  313. this.addProperties(image)
  314. },
  315. delImage(item, index) {
  316. this.imageList.splice(index, 1)
  317. for (let obj of this.imageList) {
  318. if (obj.index > item.index) {
  319. obj.index -= 1
  320. obj.x = obj.oldX
  321. obj.y = obj.oldY
  322. obj.absX = obj.index % this.colsValue
  323. obj.absY = Math.floor(obj.index / this.colsValue)
  324. this.$nextTick(() => {
  325. obj.x = obj.absX * this.viewWidth
  326. obj.y = obj.absY * this.viewWidth
  327. })
  328. }
  329. }
  330. this.add.x = (this.imageList.length % this.colsValue) * this.viewWidth + 'px'
  331. this.add.y = Math.floor(this.imageList.length / this.colsValue) * this.viewWidth + 'px'
  332. this.sortList()
  333. },
  334. delImageMp(item, index) {
  335. //#ifdef MP
  336. this.delImage(item, index)
  337. //#endif
  338. },
  339. sortList() {
  340. let list = this.imageList.slice()
  341. console.log('获取到上传图片的列表', this.imageList)
  342. list.sort((a, b) => {
  343. return a.index - b.index
  344. })
  345. for (let i = 0; i < list.length; i++) {
  346. list[i] = list[i].src
  347. }
  348. console.log('list', list)
  349. this.$emit('update:list', list)
  350. },
  351. addProperties(item) {
  352. let absX = this.imageList.length % this.colsValue
  353. let absY = Math.floor(this.imageList.length / this.colsValue)
  354. let x = absX * this.viewWidth
  355. let y = absY * this.viewWidth
  356. this.imageList.push({
  357. src: item,
  358. x,
  359. y,
  360. oldX: x,
  361. oldY: y,
  362. absX,
  363. absY,
  364. scale: 1,
  365. zIndex: 9,
  366. opacity: 1,
  367. index: this.imageList.length,
  368. id: this.guid(),
  369. disable: false,
  370. offset: 0,
  371. moveEnd: false
  372. })
  373. this.add.x = (this.imageList.length % this.colsValue) * this.viewWidth + 'px'
  374. this.add.y = Math.floor(this.imageList.length / this.colsValue) * this.viewWidth + 'px'
  375. this.sortList()
  376. },
  377. nothing() {},
  378. rpx2px(v) {
  379. return this.width * v / 750
  380. },
  381. guid() {
  382. function S4() {
  383. return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
  384. }
  385. return (S4() + S4() + "-" + S4() + "-" + S4() + "-" + S4() + "-" + S4() + S4() + S4());
  386. }
  387. }
  388. }
  389. </script>
  390. <style lang="scss" scoped>
  391. .con {
  392. // padding: 30rpx;
  393. .area {
  394. width: 100%;
  395. .view {
  396. display: flex;
  397. justify-content: center;
  398. align-items: center;
  399. .area-con {
  400. position: relative;
  401. .pre-image {
  402. width: 100%;
  403. height: 100%;
  404. }
  405. .del-con {
  406. position: absolute;
  407. top: -8rpx;
  408. right: -4rpx;
  409. padding: 0 0 20rpx 20rpx;
  410. .del-wrap {
  411. width: 36rpx;
  412. height: 36rpx;
  413. background-color: #ff0000;
  414. border-radius: 50%;
  415. display: flex;
  416. justify-content: center;
  417. align-items: center;
  418. .del-image {
  419. width: 20rpx;
  420. height: 20rpx;
  421. }
  422. }
  423. }
  424. }
  425. }
  426. .add {
  427. position: absolute;
  428. display: flex;
  429. justify-content: center;
  430. align-items: center;
  431. .add-wrap {
  432. display: flex;
  433. justify-content: center;
  434. align-items: center;
  435. background-color: #FFFFFF;
  436. }
  437. }
  438. }
  439. }
  440. </style>