shmily-drag-image.vue 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577
  1. <template>
  2. <view class="con">
  3. <template v-if="viewWidth">
  4. <movable-area class="area" :style="{ height: areaHeight }" @mouseenter="mouseenter" @mouseleave="mouseleave">
  5. <movable-view v-for="(item, index) in imageList" :key="item.id" class="view" direction="all" :y="item.y"
  6. :x="item.x" :damping="40" :disabled="item.disable" @change="onChange($event, item)"
  7. @touchstart="touchstart(item)" @mousedown="touchstart(item)" @touchend="touchend(item)"
  8. @mouseup="touchend(item)" :style="{
  9. width: viewWidth + 'px',
  10. height: viewWidth + 'px',
  11. 'z-index': item.zIndex,
  12. opacity: item.opacity
  13. }">
  14. <view class="area-con" :style="{
  15. width: childWidth,
  16. height: childWidth,
  17. borderRadius: borderRadius + 'rpx',
  18. transform: 'scale(' + item.scale + ')'
  19. }">
  20. <image class="pre-image" :src="baseUrl+item.src" mode="aspectFill"></image>
  21. <view class="del-con" @click="delImages(item, index)" @touchstart.stop="delImageMp(item, index)"
  22. @touchend.stop="nothing()" @mousedown.stop="nothing()" @mouseup.stop="nothing()">
  23. <view class="del-wrap">
  24. <image class="del-image"
  25. src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACEAAAAhCAYAAABX5MJvAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAhdEVYdENyZWF0aW9uIFRpbWUAMjAyMDowNzoyNSAyMTo1NDoyOU4TkJAAAADcSURBVFhH7ZfRCoMwDEXLvkjwwVf/bH/emmAyN6glTW9WBjsgwm28OeCLpj81Sil7zvlJ90UiONS/yY5VogsO6XrBg3IEQ5a/s8vRSWUAKmLqp2w5jz5BiNQEGMo3GbloDLtFXJ1IkaEuhAiiY6gEIqB4yqACSk9piIBiKQ8VUFpLviKg3C2rESKgWERCBZSWiEfgIfffYvrrsAgoISJ3Apy3zuTxcSxLQkV6ykNEPKVQkZEyiAiiZKgDIaC4upACSlcn5fM/+WuDCAHF1E/Z/N9AhkMZnPNDPI+UDjPIXgAQIGjNAAAAAElFTkSuQmCC">
  26. </image>
  27. </view>
  28. </view>
  29. </view>
  30. </movable-view>
  31. <view class="add" v-if="imageList.length < number"
  32. :style="{ top: add.y, left: add.x, width: viewWidth + 'px', height: viewWidth + 'px' }" @click="addImages">
  33. <view class="add-wrap" :style="{ width: childWidth, height: childWidth, borderRadius: borderRadius + 'rpx' }">
  34. <image style="width: 54rpx;height: 54rpx;"
  35. src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADYAAAA2CAYAAACMRWrdAAABIUlEQVRoQ+2a2w2DMAxFeQzWrsMUbadAsEw3S1CqVgppKwLX8BEOP4iHTXx8uUgWdVXoVhdaV0VhSmf7vr/H8V3XzY6V3P9iD+nYOI5P7/01LMI596AwoZV0TIBXIUWFXhKLFBWYSFGhhxQN6SFFQ5i4ogITKSr0cEVDekjRECauqMBEigq9U7piOk2yAti27SUe5ljlTfPEQ6KZecTvwl4P3ytvOv06R2HDMNzes7+6aRrvnHvtf50L13Lp50rx88zcvNlS3JpwKQ67XyK04nq2nFbk/LqVjin0TvmBNgQ2S4UUDcliHgpMpKjQwxUN6SFFQ5i4ogITKSr0cEVDekjRECauqMAsVoph+hVPtYr5+03p9tbYQ96xrYtT4ootbAJGVxxVTapVswAAAABJRU5ErkJggg==">
  36. </image>
  37. </view>
  38. </view>
  39. </movable-area>
  40. </template>
  41. </view>
  42. </template>
  43. <script>
  44. import config from '@/config'
  45. const baseUrl = config.baseUrl
  46. import {uploadmore} from '@/utils/common.js'
  47. export default {
  48. emits: ['input', 'update:modelValue'],
  49. props: {
  50. // 排序图片
  51. value: {
  52. type: Array,
  53. default: function() {
  54. return []
  55. }
  56. },
  57. // 排序图片
  58. modelValue: {
  59. type: Array,
  60. default: function() {
  61. return []
  62. }
  63. },
  64. // 从 list 元素对象中读取的键名
  65. keyName: {
  66. type: String,
  67. default: null
  68. },
  69. // 选择图片数量限制
  70. number: {
  71. type: Number,
  72. default: 9
  73. },
  74. // 图片父容器宽度(实际显示的图片宽度为 imageWidth / 1.1 ),单位 rpx
  75. // imageWidth > 0 则 cols 无效
  76. imageWidth: {
  77. type: Number,
  78. default: 0
  79. },
  80. // 图片列数
  81. cols: {
  82. type: Number,
  83. default: 3
  84. },
  85. // 图片圆角,单位 rpx
  86. borderRadius: {
  87. type: Number,
  88. default: 0
  89. },
  90. // 图片周围空白填充,单位 rpx
  91. padding: {
  92. type: Number,
  93. default: 10
  94. },
  95. // 拖动图片时放大倍数 [0, ∞)
  96. scale: {
  97. type: Number,
  98. default: 1.1
  99. },
  100. // 拖动图片时不透明度
  101. opacity: {
  102. type: Number,
  103. default: 0.7
  104. },
  105. // 自定义添加
  106. addImage: {
  107. type: Function,
  108. default: null
  109. },
  110. // 删除确认
  111. delImage: {
  112. type: Function,
  113. default: null
  114. }
  115. },
  116. data() {
  117. return {
  118. imageList: [],
  119. width: 0,
  120. add: {
  121. x: 0,
  122. y: 0
  123. },
  124. colsValue: 0,
  125. viewWidth: 0,
  126. tempItem: null,
  127. timer: null,
  128. changeStatus: true,
  129. preStatus: true,
  130. first: true,
  131. baseUrl:'',
  132. }
  133. },
  134. computed: {
  135. areaHeight() {
  136. let height = ''
  137. // return '355px'
  138. if (this.imageList.length < this.number) {
  139. height = (Math.ceil((this.imageList.length + 1) / this.colsValue) * this.viewWidth).toFixed() + 'px'
  140. } else {
  141. height = (Math.ceil(this.imageList.length / this.colsValue) * this.viewWidth).toFixed() + 'px'
  142. }
  143. console.log('areaHeight', height)
  144. return height
  145. },
  146. childWidth() {
  147. return this.viewWidth - this.rpx2px(this.padding) * 2 + 'px'
  148. },
  149. },
  150. watch: {
  151. value: {
  152. handler(n) {
  153. if (!this.first && this.changeStatus) {
  154. // console.log('watch', n)
  155. let flag = false
  156. for (let i = 0; i < n.length; i++) {
  157. if (flag) {
  158. this.addProperties(this.getSrc(n[i]))
  159. continue
  160. }
  161. if (this.imageList.length === i || this.imageList[i].src !== this.getSrc(n[i])) {
  162. flag = true
  163. this.imageList.splice(i)
  164. this.addProperties(this.getSrc(n[i]))
  165. }
  166. }
  167. }
  168. },
  169. deep: true
  170. },
  171. modelValue: {
  172. handler(n) {
  173. if (!this.first && this.changeStatus) {
  174. console.log('watch', n)
  175. let flag = false
  176. for (let i = 0; i < n.length; i++) {
  177. if (flag) {
  178. this.addProperties(this.getSrc(n[i]))
  179. continue
  180. }
  181. if (this.imageList.length === i || this.imageList[i].src !== this.getSrc(n[i])) {
  182. flag = true
  183. this.imageList.splice(i)
  184. this.addProperties(this.getSrc(n[i]))
  185. }
  186. }
  187. }
  188. },
  189. deep: true
  190. },
  191. },
  192. created() {
  193. this.width = uni.getSystemInfoSync().windowWidth
  194. this.baseUrl=baseUrl;
  195. },
  196. mounted() {
  197. const query = uni.createSelectorQuery().in(this)
  198. query.select('.con').boundingClientRect(data => {
  199. this.colsValue = this.cols
  200. this.viewWidth = data.width / this.cols
  201. if (this.imageWidth > 0) {
  202. this.viewWidth = this.rpx2px(this.imageWidth)
  203. this.colsValue = Math.floor(data.width / this.viewWidth)
  204. }
  205. let list = this.value
  206. // #ifdef VUE3
  207. list = this.modelValue
  208. // #endif
  209. for (let item of list) {
  210. this.addProperties(this.getSrc(item))
  211. }
  212. this.first = false
  213. })
  214. query.exec()
  215. },
  216. methods: {
  217. getSrc(item) {
  218. if(this.keyName !== null) {
  219. return item[this.keyName]
  220. }
  221. return item
  222. },
  223. onChange(e, item) {
  224. if (!item) return
  225. item.oldX = e.detail.x
  226. item.oldY = e.detail.y
  227. if (e.detail.source === 'touch') {
  228. if (item.moveEnd) {
  229. item.offset = Math.sqrt(Math.pow(item.oldX - item.absX * this.viewWidth, 2) + Math.pow(item.oldY - item
  230. .absY * this.viewWidth, 2))
  231. }
  232. let x = Math.floor((e.detail.x + this.viewWidth / 2) / this.viewWidth)
  233. if (x >= this.colsValue) return
  234. let y = Math.floor((e.detail.y + this.viewWidth / 2) / this.viewWidth)
  235. let index = this.colsValue * y + x
  236. if (item.index != index && index < this.imageList.length) {
  237. this.changeStatus = false
  238. for (let obj of this.imageList) {
  239. if (item.index > index && obj.index >= index && obj.index < item.index) {
  240. this.change(obj, 1)
  241. } else if (item.index < index && obj.index <= index && obj.index > item.index) {
  242. this.change(obj, -1)
  243. } else if (obj.id != item.id) {
  244. obj.offset = 0
  245. obj.x = obj.oldX
  246. obj.y = obj.oldY
  247. setTimeout(() => {
  248. this.$nextTick(() => {
  249. obj.x = obj.absX * this.viewWidth
  250. obj.y = obj.absY * this.viewWidth
  251. })
  252. }, 0)
  253. }
  254. }
  255. item.index = index
  256. item.absX = x
  257. item.absY = y
  258. if (!item.moveEnd) {
  259. setTimeout(() => {
  260. this.$nextTick(() => {
  261. item.x = item.absX * this.viewWidth
  262. item.y = item.absY * this.viewWidth
  263. })
  264. }, 0)
  265. }
  266. // console.log('bbb', JSON.parse(JSON.stringify(item)));
  267. this.sortList()
  268. }
  269. }
  270. },
  271. change(obj, i) {
  272. obj.index += i
  273. obj.offset = 0
  274. obj.x = obj.oldX
  275. obj.y = obj.oldY
  276. obj.absX = obj.index % this.colsValue
  277. obj.absY = Math.floor(obj.index / this.colsValue)
  278. setTimeout(() => {
  279. this.$nextTick(() => {
  280. obj.x = obj.absX * this.viewWidth
  281. obj.y = obj.absY * this.viewWidth
  282. })
  283. }, 0)
  284. },
  285. touchstart(item) {
  286. this.imageList.forEach(v => {
  287. v.zIndex = v.index + 9
  288. })
  289. item.zIndex = 99
  290. item.moveEnd = true
  291. this.tempItem = item
  292. this.timer = setTimeout(() => {
  293. item.scale = this.scale
  294. item.opacity = this.opacity
  295. clearTimeout(this.timer)
  296. this.timer = null
  297. }, 200)
  298. },
  299. touchend(item) {
  300. this.previewImage(item)
  301. item.scale = 1
  302. item.opacity = 1
  303. item.x = item.oldX
  304. item.y = item.oldY
  305. item.offset = 0
  306. item.moveEnd = false
  307. setTimeout(() => {
  308. this.$nextTick(() => {
  309. item.x = item.absX * this.viewWidth
  310. item.y = item.absY * this.viewWidth
  311. this.tempItem = null
  312. this.changeStatus = true
  313. })
  314. // console.log('ccc', JSON.parse(JSON.stringify(item)));
  315. }, 0)
  316. // console.log('ddd', JSON.parse(JSON.stringify(item)));
  317. },
  318. previewImage(item) {
  319. if (this.timer && this.preStatus && this.changeStatus && item.offset < 28.28) {
  320. clearTimeout(this.timer)
  321. this.timer = null
  322. const list = this.value || this.modelValue
  323. let srcList = list.map(v => this.baseUrl+this.getSrc(v))
  324. // console.log(list, srcList);
  325. uni.previewImage({
  326. urls: srcList,
  327. current: item.src,
  328. success: () => {
  329. this.preStatus = false
  330. setTimeout(() => {
  331. this.preStatus = true
  332. }, 600)
  333. },
  334. fail: (e) => {
  335. console.log(e);
  336. }
  337. })
  338. } else if (this.timer) {
  339. clearTimeout(this.timer)
  340. this.timer = null
  341. }
  342. },
  343. mouseenter() {
  344. //#ifdef H5
  345. this.imageList.forEach(v => {
  346. v.disable = false
  347. })
  348. //#endif
  349. },
  350. mouseleave() {
  351. //#ifdef H5
  352. if (this.tempItem) {
  353. this.imageList.forEach(v => {
  354. v.disable = true
  355. v.zIndex = v.index + 9
  356. v.offset = 0
  357. v.moveEnd = false
  358. if (v.id == this.tempItem.id) {
  359. if (this.timer) {
  360. clearTimeout(this.timer)
  361. this.timer = null
  362. }
  363. v.scale = 1
  364. v.opacity = 1
  365. v.x = v.oldX
  366. v.y = v.oldY
  367. this.$nextTick(() => {
  368. v.x = v.absX * this.viewWidth
  369. v.y = v.absY * this.viewWidth
  370. this.tempItem = null
  371. })
  372. }
  373. })
  374. this.changeStatus = true
  375. }
  376. //#endif
  377. },
  378. addImages() {
  379. var that=this;
  380. if (typeof this.addImage === 'function') {
  381. this.addImage.bind(this.$parent)()
  382. } else {
  383. let checkNumber = this.number - this.imageList.length
  384. uni.chooseImage({
  385. count: checkNumber,
  386. sourceType: ['album', 'camera'],
  387. success: res => {
  388. let count = checkNumber <= res.tempFilePaths.length ? checkNumber : res.tempFilePaths.length
  389. // for (let i = 0; i < count; i++) {
  390. // this.addProperties(res.tempFilePaths[i])
  391. // }
  392. let img= res.tempFilePaths;
  393. let imglen = res.tempFilePaths.length;
  394. var fuwufile = [];
  395. uploadmore('/common/upload',img,0,0,0,imglen,fuwufile,function(rs){
  396. // console.log(rs,123)
  397. for (let i = 0; i < count; i++) {
  398. that.addProperties(rs[i])
  399. }
  400. that.sortList()
  401. })
  402. // that.imageList = that.imageList.concat(rs);
  403. // 上传
  404. }
  405. })
  406. }
  407. },
  408. delImages(item, index) {
  409. if (typeof this.delImage === 'function') {
  410. this.delImage.bind(this.$parent)(() => {
  411. this.delImageHandle(item, index)
  412. })
  413. } else {
  414. this.delImageHandle(item, index)
  415. }
  416. },
  417. delImageHandle(item, index) {
  418. this.imageList.splice(index, 1)
  419. for (let obj of this.imageList) {
  420. if (obj.index > item.index) {
  421. obj.index -= 1
  422. obj.x = obj.oldX
  423. obj.y = obj.oldY
  424. obj.absX = obj.index % this.colsValue
  425. obj.absY = Math.floor(obj.index / this.colsValue)
  426. this.$nextTick(() => {
  427. obj.x = obj.absX * this.viewWidth
  428. obj.y = obj.absY * this.viewWidth
  429. })
  430. }
  431. }
  432. this.add.x = (this.imageList.length % this.colsValue) * this.viewWidth + 'px'
  433. this.add.y = Math.floor(this.imageList.length / this.colsValue) * this.viewWidth + 'px'
  434. this.sortList()
  435. },
  436. delImageMp(item, index) {
  437. //#ifdef MP
  438. this.delImages(item, index)
  439. //#endif
  440. },
  441. sortList() {
  442. console.log('sortList');
  443. const result = []
  444. let source = this.value
  445. // #ifdef VUE3
  446. source = this.modelValue
  447. // #endif
  448. let list = this.imageList.slice()
  449. list.sort((a, b) => {
  450. return a.index - b.index
  451. })
  452. for (let s of list) {
  453. let item = source.find(d => this.getSrc(d) == s.src)
  454. if (item) {
  455. result.push(item)
  456. } else {
  457. if(this.keyName !== null) {
  458. result.push({
  459. [this.keyName]: s.src
  460. })
  461. } else {
  462. result.push(s.src)
  463. }
  464. }
  465. }
  466. this.$emit("input", result);
  467. this.$emit("update:modelValue", result);
  468. },
  469. addProperties(item) {
  470. let absX = this.imageList.length % this.colsValue
  471. let absY = Math.floor(this.imageList.length / this.colsValue)
  472. let x = absX * this.viewWidth
  473. let y = absY * this.viewWidth
  474. this.imageList.push({
  475. src: item,
  476. x,
  477. y,
  478. oldX: x,
  479. oldY: y,
  480. absX,
  481. absY,
  482. scale: 1,
  483. zIndex: 9,
  484. opacity: 1,
  485. index: this.imageList.length,
  486. id: this.guid(16),
  487. disable: false,
  488. offset: 0,
  489. moveEnd: false
  490. })
  491. this.add.x = (this.imageList.length % this.colsValue) * this.viewWidth + 'px'
  492. this.add.y = Math.floor(this.imageList.length / this.colsValue) * this.viewWidth + 'px'
  493. },
  494. nothing() {},
  495. rpx2px(v) {
  496. return this.width * v / 750
  497. },
  498. guid(len = 32) {
  499. const chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('')
  500. const uuid = []
  501. const radix = chars.length
  502. for (let i = 0; i < len; i++) uuid[i] = chars[0 | Math.random() * radix]
  503. uuid.shift()
  504. return `u${uuid.join('')}`
  505. }
  506. }
  507. }
  508. </script>
  509. <style lang="scss" scoped>
  510. .con {
  511. // padding: 30rpx;
  512. .area {
  513. width: 100%;
  514. .view {
  515. display: flex;
  516. justify-content: center;
  517. align-items: center;
  518. .area-con {
  519. position: relative;
  520. overflow: hidden;
  521. .pre-image {
  522. width: 100%;
  523. height: 100%;
  524. }
  525. .del-con {
  526. position: absolute;
  527. top: 0rpx;
  528. right: 0rpx;
  529. padding: 0 0 20rpx 20rpx;
  530. .del-wrap {
  531. width: 36rpx;
  532. height: 36rpx;
  533. background-color: rgba(0, 0, 0, 0.4);
  534. border-radius: 0 0 0 10rpx;
  535. display: flex;
  536. justify-content: center;
  537. align-items: center;
  538. .del-image {
  539. width: 20rpx;
  540. height: 20rpx;
  541. }
  542. }
  543. }
  544. }
  545. }
  546. .add {
  547. position: absolute;
  548. display: flex;
  549. justify-content: center;
  550. align-items: center;
  551. .add-wrap {
  552. display: flex;
  553. justify-content: center;
  554. align-items: center;
  555. background-color: #eeeeee;
  556. }
  557. }
  558. }
  559. }
  560. </style>