en-upload.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670
  1. <template>
  2. <view class="con" :class="{'no-con':!showLabel}">
  3. <view class="con-title" v-if="showLabel">{{label}}</view>
  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"
  7. :disabled="item.disable" @change="onChange($event, item)" @touchstart="touchstart(item)"
  8. @mousedown="touchstart(item)" @touchend="touchend(item)" @mouseup="touchend(item)"
  9. :style="{ width: viewWidth + 'px', height: viewWidth + 'px', 'z-index': item.zIndex, opacity: item.opacity }">
  10. <view class="area-con" @click.stop="examineImgOne(index)"
  11. :style="{ width: childWidth, height: childWidth, transform: 'scale(' + item.scale + ')' }">
  12. <image class="pre-image"
  13. :src="item.src+(imgType===1?'':'?x-oss-process=video/snapshot,t_7000,f_jpg,w_600,h_0,m_fast')"
  14. mode="aspectFill"></image>
  15. <text v-if="imgType===2" class="bf-img iconfont"> &#xea82;</text>
  16. <view class="del-con" @click.stop="delImage(item, index)"
  17. @touchstart.stop="delImageMp(item, index)" @touchend.stop="nothing()"
  18. @mousedown.stop="nothing()" @mouseup.stop="nothing()">
  19. <view class="del-wrap">
  20. <!-- <image class="del-image"-->
  21. <!-- src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACEAAAAhCAYAAABX5MJvAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAhdEVYdENyZWF0aW9uIFRpbWUAMjAyMDowNzoyNSAyMTo1NDoyOU4TkJAAAADcSURBVFhH7ZfRCoMwDEXLvkjwwVf/bH/emmAyN6glTW9WBjsgwm28OeCLpj81Sil7zvlJ90UiONS/yY5VogsO6XrBg3IEQ5a/s8vRSWUAKmLqp2w5jz5BiNQEGMo3GbloDLtFXJ1IkaEuhAiiY6gEIqB4yqACSk9piIBiKQ8VUFpLviKg3C2rESKgWERCBZSWiEfgIfffYvrrsAgoISJ3Apy3zuTxcSxLQkV6ykNEPKVQkZEyiAiiZKgDIaC4upACSlcn5fM/+WuDCAHF1E/Z/N9AhkMZnPNDPI+UDjPIXgAQIGjNAAAAAElFTkSuQmCC">-->
  22. <!-- </image>-->
  23. <text class="del-image iconfont"> &#xe603;</text>
  24. </view>
  25. </view>
  26. </view>
  27. </movable-view>
  28. </block>
  29. <view class="add" v-if="imageList.length < number"
  30. :style="{ top: add.y, left: add.x, width: viewWidth + 'px', height: viewWidth + 'px' }"
  31. @click="showUploadingImg(true,0)">
  32. <view class="add-wrap" :style="{ width: childWidth, height: childWidth }">
  33. <view class="video-data">
  34. <text class="video-img iconfont">&#xe658;</text>
  35. </view>
  36. </view>
  37. </view>
  38. <uni-popup ref="popup" :safeArea="false" type="bottom" @change="closePopup">
  39. <view class="popup-block">
  40. <view class="popup-row" @click="selectMultimedia(1)">拍照</view>
  41. <view class="popup-row" @click="selectMultimedia(2)">从手机里面选择</view>
  42. <view class="popup-row" @click="showUploadingImg(false,0)">取消</view>
  43. </view>
  44. </uni-popup>
  45. </movable-area>
  46. </view>
  47. </template>
  48. <script>
  49. import tools from "@/service/tools";
  50. import txUploadFile from "@/service/txOssSts";
  51. export default {
  52. components: {
  53. },
  54. data() {
  55. return {
  56. imageList: [],
  57. previewList: [],
  58. width: 0,
  59. add: {
  60. x: 0,
  61. y: 0
  62. },
  63. current: 0,
  64. colsValue: 0,
  65. viewWidth: 0,
  66. tempItem: null,
  67. timer: null,
  68. changeStatus: true,
  69. preStatus: true,
  70. ossClient: null,
  71. isStart: true,
  72. isUploading: false
  73. }
  74. },
  75. props: {
  76. value: {
  77. type: Array,
  78. default: function() {
  79. return []
  80. }
  81. },
  82. showLabel: {
  83. type: Boolean,
  84. default: true
  85. },
  86. label: {
  87. type: String,
  88. default: '标题'
  89. },
  90. // 返回排序后图片
  91. list: {
  92. type: Array,
  93. default: function() {
  94. return []
  95. }
  96. },
  97. imgType: {
  98. type: Number,
  99. default: 1
  100. },
  101. fileNum: {
  102. type: Number,
  103. default: 9
  104. },
  105. // 选择图片数量限制
  106. number: {
  107. type: Number,
  108. default: 100
  109. },
  110. // 图片父容器宽度(实际显示的图片宽度为 imageWidth / 1.1 ),单位 rpx
  111. imageWidth: {
  112. type: Number,
  113. default: 180
  114. },
  115. // 图片列数(cols > 0 则 imageWidth 无效)
  116. cols: {
  117. type: Number,
  118. default: 0
  119. },
  120. // 图片周围空白填充,单位 rpx
  121. padding: {
  122. type: Number,
  123. default: 10
  124. },
  125. // 拖动图片时放大倍数 [0, ∞)
  126. scale: {
  127. type: Number,
  128. default: 1.1
  129. },
  130. // 拖动图片时不透明度
  131. opacity: {
  132. type: Number,
  133. default: 0.7
  134. },
  135. // 自定义添加(需配合 @aaddImage 事件使用)
  136. custom: {
  137. type: Boolean,
  138. default: false
  139. }
  140. },
  141. watch: {
  142. 'value': function() {
  143. this.startValue()
  144. }
  145. },
  146. computed: {
  147. areaHeight() {
  148. if (this.imageList.length < this.number) {
  149. return Math.ceil((this.imageList.length + 1) / this.colsValue) * this.viewWidth + 'px'
  150. } else {
  151. return Math.ceil(this.imageList.length / this.colsValue) * this.viewWidth + 'px'
  152. }
  153. },
  154. childWidth() {
  155. return this.viewWidth - this.rpx2px(this.padding) * 2 + 'px'
  156. },
  157. },
  158. created() {
  159. this.width = uni.getSystemInfoSync().windowWidth
  160. this.viewWidth = this.rpx2px(this.imageWidth)
  161. },
  162. mounted() {
  163. const query = uni.createSelectorQuery().in(this)
  164. query.select('.area').boundingClientRect(data => {
  165. if (data) {
  166. this.colsValue = Math.floor(data.width / this.viewWidth)
  167. if (this.cols > 0) {
  168. this.colsValue = this.cols
  169. this.viewWidth = data.width / this.cols
  170. }
  171. for (let item of this.list) {
  172. this.addProperties(item)
  173. }
  174. }
  175. })
  176. query.exec()
  177. this.startValue()
  178. },
  179. methods: {
  180. showUploadingImg(showImg) {
  181. if (showImg) {
  182. if (this.isUploading) {
  183. return
  184. }
  185. this.isUploading = true
  186. this.$refs.popup.open("bottom");
  187. } else {
  188. this.$refs.popup.close();
  189. this.isUploading = false
  190. }
  191. },
  192. closePopup(e) {
  193. if (e.show === false) {
  194. this.isUploading = false
  195. }
  196. },
  197. startValue() {
  198. if (!this.isStart) {
  199. return
  200. }
  201. if (this.value.length > 0) {
  202. this.isStart = false
  203. setTimeout(() => {
  204. this.startList(this.value)
  205. }, 100)
  206. }
  207. },
  208. examineImgOne(index) {
  209. // this.$emit('examineImgOne',index)
  210. // this.previewList = this.list
  211. // this.current = index
  212. // this.$refs.previewImage.open(index);
  213. let imgList = []
  214. this.imageList.forEach((item) => {
  215. console.log(item)
  216. imgList.push(item.src)
  217. })
  218. uni.previewImage({
  219. urls: imgList,
  220. current: index,
  221. success: () => {
  222. }
  223. })
  224. },
  225. startList(imgList) {
  226. if (imgList.length <= 0) {
  227. let maxImageNum = this.imageList.length
  228. if (maxImageNum > 0) {
  229. for (let i = maxImageNum; i > 0; --i) {
  230. let keyNum = i - 1;
  231. this.delImage(this.imageList[keyNum], keyNum)
  232. }
  233. }
  234. } else {
  235. imgList.forEach((item) => {
  236. this.addProperties(item)
  237. })
  238. }
  239. // this.sortList()
  240. },
  241. onChange(e, item) {
  242. if (!item) return
  243. item.oldX = e.detail.x
  244. item.oldY = e.detail.y
  245. if (e.detail.source === 'touch') {
  246. if (item.moveEnd) {
  247. item.offset = Math.sqrt(Math.pow(item.oldX - item.absX * this.viewWidth, 2) + Math.pow(item.oldY -
  248. item
  249. .absY * this.viewWidth, 2))
  250. }
  251. let x = Math.floor((e.detail.x + this.viewWidth / 2) / this.viewWidth)
  252. if (x >= this.colsValue) return
  253. let y = Math.floor((e.detail.y + this.viewWidth / 2) / this.viewWidth)
  254. let index = this.colsValue * y + x
  255. if (item.index != index && index < this.imageList.length) {
  256. this.changeStatus = false
  257. for (let obj of this.imageList) {
  258. if (item.index > index && obj.index >= index && obj.index < item.index) {
  259. this.change(obj, 1)
  260. } else if (item.index < index && obj.index <= index && obj.index > item.index) {
  261. this.change(obj, -1)
  262. } else if (obj.id != item.id) {
  263. obj.offset = 0
  264. obj.x = obj.oldX
  265. obj.y = obj.oldY
  266. setTimeout(() => {
  267. this.$nextTick(() => {
  268. obj.x = obj.absX * this.viewWidth
  269. obj.y = obj.absY * this.viewWidth
  270. })
  271. }, 0)
  272. }
  273. }
  274. item.index = index
  275. item.absX = x
  276. item.absY = y
  277. this.sortList()
  278. }
  279. }
  280. },
  281. change(obj, i) {
  282. obj.index += i
  283. obj.offset = 0
  284. obj.x = obj.oldX
  285. obj.y = obj.oldY
  286. obj.absX = obj.index % this.colsValue
  287. obj.absY = Math.floor(obj.index / this.colsValue)
  288. setTimeout(() => {
  289. this.$nextTick(() => {
  290. obj.x = obj.absX * this.viewWidth
  291. obj.y = obj.absY * this.viewWidth
  292. })
  293. }, 0)
  294. },
  295. touchstart(item) {
  296. this.imageList.forEach(v => {
  297. v.zIndex = v.index + 9
  298. })
  299. item.zIndex = 99
  300. item.moveEnd = true
  301. this.tempItem = item
  302. this.timer = setTimeout(() => {
  303. item.scale = this.scale
  304. item.opacity = this.opacity
  305. clearTimeout(this.timer)
  306. this.timer = null
  307. }, 200)
  308. },
  309. touchend(item) {
  310. this.previewImage(item)
  311. item.scale = 1
  312. item.opacity = 1
  313. item.x = item.oldX
  314. item.y = item.oldY
  315. item.offset = 0
  316. item.moveEnd = false
  317. setTimeout(() => {
  318. this.$nextTick(() => {
  319. item.x = item.absX * this.viewWidth
  320. item.y = item.absY * this.viewWidth
  321. this.tempItem = null
  322. this.changeStatus = true
  323. })
  324. }, 0)
  325. },
  326. previewImage(item) {
  327. console.log(item)
  328. if (this.timer && this.preStatus && this.changeStatus && item.offset < 28.28) {
  329. clearTimeout(this.timer)
  330. this.timer = null
  331. let src = this.list.findIndex(v => v === item.src)
  332. uni.previewImage({
  333. urls: this.list,
  334. current: src,
  335. success: () => {
  336. this.preStatus = false
  337. setTimeout(() => {
  338. this.preStatus = true
  339. }, 600)
  340. }
  341. })
  342. } else if (this.timer) {
  343. clearTimeout(this.timer)
  344. this.timer = null
  345. }
  346. },
  347. mouseenter() {
  348. //#ifdef H5
  349. this.imageList.forEach(v => {
  350. v.disable = false
  351. })
  352. //#endif
  353. },
  354. mouseleave() {
  355. //#ifdef H5
  356. if (this.tempItem) {
  357. this.imageList.forEach(v => {
  358. v.disable = true
  359. v.zIndex = v.index + 1
  360. v.offset = 0
  361. v.moveEnd = false
  362. if (v.id == this.tempItem.id) {
  363. if (this.timer) {
  364. clearTimeout(this.timer)
  365. this.timer = null
  366. }
  367. v.scale = 1
  368. v.opacity = 1
  369. v.x = v.oldX
  370. v.y = v.oldY
  371. this.$nextTick(() => {
  372. v.x = v.absX * this.viewWidth
  373. v.y = v.absY * this.viewWidth
  374. this.tempItem = null
  375. })
  376. }
  377. })
  378. this.changeStatus = true
  379. }
  380. //#endif
  381. },
  382. selectMultimedia(fileType) {
  383. if (this.imgType === 1) {
  384. //图片选择
  385. this.uploadingImg(fileType);
  386. } else {
  387. //视频选择
  388. this.uploadingVideo(fileType);
  389. }
  390. },
  391. uploadingImg(sourceType) {
  392. if (this.list.length > 100) {
  393. tools.error('最多上传100张')
  394. return;
  395. }
  396. uni.chooseImage({
  397. count: this.fileNum, //默认9
  398. sizeType: 'compressed', //可以指定是原图还是压缩图
  399. sourceType: [sourceType === 1 ? 'camera' : 'album'],
  400. success: (res) => {
  401. this.showUploadingImg(false)
  402. if (res.tempFiles !== undefined) {
  403. res.tempFiles.forEach((file) => {
  404. if (file.size > (1048576 * 3)) {
  405. tools.error('图片最大3MB')
  406. } else {
  407. if (this.list.length > 100) {
  408. tools.error('最多上传100张')
  409. } else {
  410. if (tools.getPlatform() === 'H5') {
  411. this.uploadingFile(file);
  412. } else {
  413. this.uploadingFile(file.path);
  414. }
  415. }
  416. }
  417. })
  418. }
  419. },
  420. fail: (e) => {
  421. console.log(e)
  422. }
  423. });
  424. },
  425. uploadingVideo(sourceType) {
  426. uni.chooseVideo({
  427. sourceType: [sourceType === 1 ? 'camera' : 'album'],
  428. maxDuration: 60,
  429. success: (res) => {
  430. this.showUploadingImg(false)
  431. this.uploadingFile(res.tempFile);
  432. }
  433. });
  434. },
  435. uploadingFile(file) {
  436. //图片上传
  437. tools.showLoading();
  438. this.isStart = false
  439. txUploadFile(file).then((res) => {
  440. if (res.Location) {
  441. this.addProperties(res.Location)
  442. tools.hideLoading();
  443. } else {
  444. tools.hideLoading();
  445. tools.error('上传失败')
  446. }
  447. }).catch((err) => {
  448. tools.hideLoading();
  449. })
  450. },
  451. addImages() {
  452. if (this.custom) {
  453. this.$emit('addImage')
  454. } else {
  455. let checkNumber = this.number - this.imageList.length
  456. uni.chooseImage({
  457. count: checkNumber,
  458. sourceType: ['album', 'camera'],
  459. success: res => {
  460. let count = checkNumber <= res.tempFilePaths.length ? checkNumber : res
  461. .tempFilePaths.length
  462. for (let i = 0; i < count; i++) {
  463. this.addProperties(res.tempFilePaths[i])
  464. }
  465. }
  466. })
  467. }
  468. },
  469. addImage(image) {
  470. this.addProperties(image)
  471. },
  472. delImage(item, index) {
  473. this.imageList.splice(index, 1)
  474. for (let obj of this.imageList) {
  475. if (obj.index > item.index) {
  476. obj.index -= 1
  477. obj.x = obj.oldX
  478. obj.y = obj.oldY
  479. obj.absX = obj.index % this.colsValue
  480. obj.absY = Math.floor(obj.index / this.colsValue)
  481. this.$nextTick(() => {
  482. obj.x = obj.absX * this.viewWidth
  483. obj.y = obj.absY * this.viewWidth
  484. })
  485. }
  486. }
  487. this.add.x = (this.imageList.length % this.colsValue) * this.viewWidth + 'px'
  488. this.add.y = Math.floor(this.imageList.length / this.colsValue) * this.viewWidth + 'px'
  489. this.sortList()
  490. },
  491. delImageMp(item, index) {
  492. //#ifdef MP
  493. this.delImage(item, index)
  494. //#endif
  495. },
  496. sortList() {
  497. let list = this.imageList.slice()
  498. list.sort((a, b) => {
  499. return a.index - b.index
  500. })
  501. for (let i = 0; i < list.length; i++) {
  502. list[i] = list[i].src
  503. }
  504. // this.$emit('update:list', list)
  505. this.$emit('input', list)
  506. },
  507. addProperties(item) {
  508. console.log('------------------addProperties')
  509. console.log(item)
  510. let absX = this.imageList.length % this.colsValue
  511. let absY = Math.floor(this.imageList.length / this.colsValue)
  512. let x = absX * this.viewWidth
  513. let y = absY * this.viewWidth
  514. this.imageList.push({
  515. src: item,
  516. x,
  517. y,
  518. oldX: x,
  519. oldY: y,
  520. absX,
  521. absY,
  522. scale: 1,
  523. zIndex: 9,
  524. opacity: 1,
  525. index: this.imageList.length,
  526. id: this.guid(),
  527. disable: false,
  528. offset: 0,
  529. moveEnd: false
  530. })
  531. this.add.x = (this.imageList.length % this.colsValue) * this.viewWidth + 'px'
  532. this.add.y = Math.floor(this.imageList.length / this.colsValue) * this.viewWidth + 'px'
  533. this.sortList()
  534. },
  535. nothing() {},
  536. rpx2px(v) {
  537. return this.width * v / 750
  538. },
  539. guid() {
  540. function S4() {
  541. return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
  542. }
  543. return (S4() + S4() + "-" + S4() + "-" + S4() + "-" + S4() + "-" + S4() + S4() + S4());
  544. }
  545. }
  546. }
  547. </script>
  548. <style lang="scss" scoped>
  549. @import url("../../static/css/en-common.css");
  550. @import "@/static/css/wh-common";
  551. .con {
  552. padding: 30rpx 0;
  553. border-bottom: 2rpx solid #F0F0F0;
  554. .con-title {
  555. font-size: 28rpx;
  556. color: #333333;
  557. padding-bottom: 10rpx;
  558. }
  559. .area {
  560. width: 100%;
  561. .view {
  562. display: flex;
  563. justify-content: center;
  564. align-items: center;
  565. .area-con {
  566. position: relative;
  567. .pre-image {
  568. width: 100%;
  569. height: 100%;
  570. border-radius: 10rpx;
  571. }
  572. .bf-img {
  573. width: 40rpx;
  574. height: 40rpx;
  575. position: absolute;
  576. left: 50%;
  577. top: 50%;
  578. //margin: -20rpx 0 0 -20rpx;
  579. z-index: 2;
  580. }
  581. .del-con {
  582. position: absolute;
  583. top: 5rpx;
  584. right: 5rpx;
  585. //padding: 0 0 20rpx 20rpx;
  586. .del-wrap {
  587. width: 36rpx;
  588. height: 36rpx;
  589. //background-color: rgba(0, 0, 0, 0.4);
  590. border-radius: 0 0 0 10rpx;
  591. display: flex;
  592. justify-content: center;
  593. align-items: center;
  594. .del-image {
  595. //width: 20rpx;
  596. //height: 20rpx;
  597. }
  598. }
  599. }
  600. }
  601. }
  602. .add {
  603. position: absolute;
  604. display: flex;
  605. justify-content: center;
  606. align-items: center;
  607. .add-wrap {
  608. // display: flex;
  609. // justify-content: center;
  610. // align-items: center;
  611. background: #F7F7F7;
  612. border-radius: 10rpx;
  613. .add-img {
  614. width: 80rpx;
  615. height: 80rpx;
  616. margin: 0 auto;
  617. padding-top: 44rpx;
  618. }
  619. .names {
  620. color: #6F6F6F;
  621. font-size: 28rpx;
  622. text-align: center;
  623. }
  624. .video-data {
  625. width: 160rpx;
  626. height: 160rpx;
  627. //background-color: #fff;
  628. text-align: center;
  629. line-height: 160rpx;
  630. .video-img {
  631. font-size: 80rpx;
  632. color: #999;
  633. }
  634. }
  635. }
  636. }
  637. }
  638. }
  639. .no-con {
  640. padding: 0;
  641. border: none;
  642. }
  643. </style>