xfx-image-upload.vue 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700
  1. <template>
  2. <view class="xfx-image-upload-list">
  3. <view class="xfx-image-upload-Item" v-for="(item,index) in uploadLists" :key="item.url">
  4. <view class="xfx-image-upload-Item-video" v-if="(!/.(gif|jpg|jpeg|png|gif|jpg|png)$/i.test(item))">
  5. <image :disabled="false" :controls="false"
  6. :src="item+'?x-oss-process=video/snapshot,t_0,m_fast,f_jpg,ar_auto'" mode="">
  7. <view class="xfx-image-upload-Item-video-fixed" @click="previewVideo(item)">
  8. <!-- <uni-icons type='videocam' style="font-size:60rpx"> </uni-icons> -->
  9. <!-- <u-icon size='80' name='play-circle-fill'></u-icon> -->
  10. <image class="playBtn" src="/static/bofang.png" mode="">
  11. </view>
  12. <!-- <view class="xfx-image-upload-Item-del-cover" v-if="remove && previewVideoSrc==''"
  13. @click="imgDel(index)">×</view> -->
  14. </image>
  15. </view>
  16. <image v-else :src="item" @click="imgPreview(item)"></image>
  17. <view class="xfx-image-upload-Item-del" v-if="remove" @click="imgDel(index)">×</view>
  18. </view>
  19. <!-- 上传按钮 -->
  20. <view class="xfx-image-upload-Item xfx-image-upload-Item-add" v-if="uploadLists.length<max && add"
  21. @click="chooseFile">
  22. <!-- <uni-icons type='camera' style="font-size:60rpx"> </uni-icons> -->
  23. <image src="/static/add.png" style="width: 40rpx;height: 40rpx;margin-top: 15rpx;"></image>
  24. <!-- <u-icon name='camera-fill' size='45'></u-icon> -->
  25. <!-- <text class="text-upload">点击上传</text> -->
  26. </view>
  27. <!-- flex布局 起占位作用 -->
  28. <!-- <view class="xfx-image-upload-Item" v-if="uploadLists.length<max-1">
  29. </view> -->
  30. <view class="preview-full" v-if="previewVideoSrc!=''">
  31. <video :autoplay="true" :src="previewVideoSrc" :show-fullscreen-btn="false">
  32. <cover-view style="" class="preview-full-close" @click="previewVideoClose"> ×
  33. </cover-view>
  34. </video>
  35. </view>
  36. <!-- -->
  37. </view>
  38. </template>
  39. <style>
  40. </style>
  41. <script>
  42. //请求头token
  43. import {
  44. getToken
  45. } from '@/utils/auth'
  46. import UniIcons from '@/components/uni-icons/uni-icons.vue'
  47. export default {
  48. name: 'xfx-image-upload',
  49. props: {
  50. max: { //展示图片最大值
  51. type: Number,
  52. default: 6,
  53. },
  54. chooseNum: { //选择图片数
  55. type: Number,
  56. default: 6,
  57. },
  58. name: { //发到后台的文件参数名
  59. type: String,
  60. default: 'file',
  61. },
  62. remove: { //是否展示删除按钮
  63. type: Boolean,
  64. default: true,
  65. },
  66. add: { //是否展示添加按钮
  67. type: Boolean,
  68. default: true,
  69. },
  70. disabled: { //是否禁用
  71. type: Boolean,
  72. default: false,
  73. },
  74. sourceType: { //选择照片来源 【ps:H5就别费劲了,设置了也没用。不是我说的,官方文档就这样!!!】
  75. type: Array,
  76. default: () => ['album', 'camera'],
  77. },
  78. action: { //上传地址
  79. type: String,
  80. default: '',
  81. },
  82. headers: { //上传的请求头部
  83. type: Object,
  84. default: () => {
  85. return {
  86. Authorization: getToken()
  87. }
  88. },
  89. },
  90. formData: { //HTTP 请求中其他额外的 form data
  91. type: Object,
  92. default: () => {},
  93. },
  94. compress: { //是否需要压缩
  95. type: Boolean,
  96. default: true,
  97. },
  98. quality: { //压缩质量,范围0~100
  99. type: Number,
  100. default: 80,
  101. },
  102. value: { //受控图片列表
  103. type: Array,
  104. default: () => [],
  105. },
  106. uploadSuccess: {
  107. default: (res) => {
  108. return {
  109. success: false,
  110. url: ''
  111. }
  112. },
  113. },
  114. mediaType: { //文件类型 image/video/all
  115. type: String,
  116. default: 'image',
  117. },
  118. maxDuration: { //拍摄视频最长拍摄时间,单位秒。最长支持 60 秒。 (只针对拍摄视频有用)
  119. type: Number,
  120. default: 60,
  121. },
  122. camera: { //'front'、'back',默认'back'(只针对拍摄视频有用)
  123. type: String,
  124. default: 'back',
  125. },
  126. },
  127. components: {
  128. UniIcons
  129. },
  130. data() {
  131. return {
  132. uploadLists: [],
  133. mediaTypeData: ['image', 'video', 'all'],
  134. previewVideoSrc: '',
  135. // header:{
  136. // 'Authorization':''
  137. // }
  138. }
  139. },
  140. mounted: function() {
  141. console.log(this.action)
  142. this.headers.Authorization = getToken()
  143. console.log(this.headers)
  144. this.$nextTick(function() {
  145. this.uploadLists = this.value;
  146. if (this.mediaTypeData.indexOf(this.mediaType) == -1) {
  147. uni.showModal({
  148. title: '提示',
  149. content: 'mediaType参数不正确',
  150. showCancel: false,
  151. success: function(res) {
  152. if (res.confirm) {
  153. //console.log('用户点击确定');
  154. } else if (res.cancel) {
  155. //console.log('用户点击取消');
  156. }
  157. }
  158. });
  159. }
  160. });
  161. },
  162. watch: {
  163. value(val, oldVal) {
  164. console.log('value', val, oldVal)
  165. this.uploadLists = val;
  166. },
  167. },
  168. methods: {
  169. previewVideo(src) {
  170. this.previewVideoSrc = src;
  171. // this.previewVideoSrc =
  172. // 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-fbd63a76-dc76-485c-b711-f79f2986daeb/ba804d82-860b-4d1a-a706-5a4c8ce137c3.mp4'
  173. },
  174. previewVideoClose() {
  175. this.previewVideoSrc = ''
  176. console.log('previewVideoClose', this.previewVideoSrc)
  177. },
  178. imgDel(index) {
  179. let delUrl = this.uploadLists[index]
  180. this.uploadLists.splice(index, 1)
  181. this.$emit("input", this.uploadLists);
  182. this.$emit("imgDelete", {
  183. del: delUrl,
  184. tempFilePaths: this.uploadLists
  185. });
  186. return
  187. uni.showModal({
  188. title: '提示',
  189. content: '您确定要删除么?',
  190. success: (res) => {
  191. if (res.confirm) {
  192. // this.uploadLists.splice(index, 1)
  193. // this.$emit("input", this.uploadLists);
  194. // this.$emit("imgDelete", this.uploadLists);
  195. } else if (res.cancel) {}
  196. }
  197. });
  198. },
  199. imgPreview(index) {
  200. var imgData = this.uploadLists.filter(item => /.(gif|jpg|jpeg|png|gif|jpg|png)$/i.test(item)) //只预览图片的
  201. uni.previewImage({
  202. urls: imgData,
  203. current: index,
  204. loop: true,
  205. });
  206. },
  207. chooseFile() {
  208. if (this.disabled) {
  209. return false;
  210. }
  211. switch (this.mediaTypeData.indexOf(this.mediaType)) {
  212. case 1: //视频
  213. this.videoAdd();
  214. break;
  215. case 2: //全部
  216. uni.showActionSheet({
  217. itemList: ['相册', '视频'],
  218. success: (res) => {
  219. if (res.tapIndex == 1) {
  220. this.videoAdd();
  221. } else if (res.tapIndex == 0) {
  222. this.imgAdd();
  223. }
  224. },
  225. fail: (res) => {
  226. console.log(res.errMsg);
  227. }
  228. });
  229. break;
  230. default: //图片
  231. this.imgAdd();
  232. break;
  233. }
  234. //if(this.mediaType=='image'){
  235. },
  236. videoAdd() {
  237. console.log('videoAdd')
  238. let nowNum = Math.abs(this.uploadLists.length - this.max);
  239. let thisNum = (this.chooseNum > nowNum ? nowNum : this.chooseNum) //可选数量
  240. uni.chooseVideo({
  241. compressed: this.compress,
  242. sourceType: this.sourceType,
  243. camera: this.camera,
  244. maxDuration: this.maxDuration,
  245. success: (res) => {
  246. console.log('videoAdd', res)
  247. console.log(res.tempFilePath)
  248. this.chooseSuccessMethod([res.tempFilePath], 1)
  249. //this.imgUpload([res.tempFilePath]);
  250. //console.log('tempFiles', res)
  251. // if (this.action == '') { //未配置上传路径
  252. // this.$emit("chooseSuccess", res.tempFilePaths);
  253. // } else {
  254. // if (this.compress && (res.tempFiles[0].size / 1024 > 1025)) { //设置了需要压缩 并且 文件大于1M,进行压缩上传
  255. // this.imgCompress(res.tempFilePaths);
  256. // } else {
  257. // this.imgUpload(res.tempFilePaths);
  258. // }
  259. // }
  260. }
  261. });
  262. },
  263. imgAdd() {
  264. console.log('imgAdd')
  265. let nowNum = Math.abs(this.uploadLists.length - this.max);
  266. let thisNum = (this.chooseNum > nowNum ? nowNum : this.chooseNum) //可选数量
  267. console.log('nowNum', nowNum)
  268. console.log('thisNum', thisNum)
  269. // #ifdef APP-PLUS
  270. if (this.sourceType.length > 1) {
  271. uni.showActionSheet({
  272. itemList: ['拍摄', '从手机相册选择'],
  273. success: (res) => {
  274. if (res.tapIndex == 1) {
  275. this.appGallery(thisNum);
  276. } else if (res.tapIndex == 0) {
  277. this.appCamera();
  278. }
  279. },
  280. fail: (res) => {
  281. console.log(res.errMsg);
  282. }
  283. });
  284. }
  285. if (this.sourceType.length == 1 && this.sourceType.indexOf('album') > -1) {
  286. this.appGallery(thisNum);
  287. }
  288. if (this.sourceType.length == 1 && this.sourceType.indexOf('camera') > -1) {
  289. this.appCamera();
  290. }
  291. // #endif
  292. //#ifndef APP-PLUS
  293. uni.chooseImage({
  294. count: thisNum,
  295. sizeType: ['original', 'compressed'], //可以指定是原图还是压缩图,默认二者都有
  296. sourceType: this.sourceType,
  297. success: (res) => {
  298. this.chooseSuccessMethod(res.tempFilePaths, 0)
  299. //console.log('tempFiles', res)
  300. // if (this.action == '') { //未配置上传路径
  301. // this.$emit("chooseSuccess", res.tempFilePaths);
  302. // } else {
  303. // if (this.compress && (res.tempFiles[0].size / 1024 > 1025)) { //设置了需要压缩 并且 文件大于1M,进行压缩上传
  304. // this.imgCompress(res.tempFilePaths);
  305. // } else {
  306. // this.imgUpload(res.tempFilePaths);
  307. // }
  308. // }
  309. }
  310. });
  311. // #endif
  312. },
  313. appCamera() {
  314. var cmr = plus.camera.getCamera();
  315. var res = cmr.supportedImageResolutions[0];
  316. var fmt = cmr.supportedImageFormats[0];
  317. //console.log("Resolution: " + res + ", Format: " + fmt);
  318. cmr.captureImage((path) => {
  319. //alert("Capture image success: " + path);
  320. this.chooseSuccessMethod([path], 0)
  321. },
  322. (error) => {
  323. //alert("Capture image failed: " + error.message);
  324. console.log("Capture image failed: " + error.message)
  325. }, {
  326. resolution: res,
  327. format: fmt
  328. }
  329. );
  330. },
  331. appGallery(maxNum) {
  332. plus.gallery.pick((res) => {
  333. this.chooseSuccessMethod(res.files, 0)
  334. }, function(e) {
  335. //console.log("取消选择图片");
  336. }, {
  337. filter: "image",
  338. multiple: true,
  339. maximum: maxNum
  340. });
  341. },
  342. chooseSuccessMethod(filePaths, type) {
  343. if (this.action == '') { //未配置上传路径
  344. // this.value.push({url:filePaths[0]})
  345. // console.log(this.value)
  346. // this.uploadLists.push({url:filePaths[0]})
  347. this.$emit("chooseSuccess", filePaths, type); //filePaths 路径 type 0 为图片 1为视频
  348. } else {
  349. if (type == 1) {
  350. this.imgUpload(filePaths);
  351. } else {
  352. if (this.compress) { //设置了需要压缩
  353. this.imgCompress(filePaths);
  354. } else {
  355. this.imgUpload(filePaths);
  356. }
  357. }
  358. }
  359. },
  360. imgCompress(tempFilePaths) {
  361. uni.showLoading({
  362. title: '压缩中...'
  363. });
  364. let compressImgs = [];
  365. let results = [];
  366. tempFilePaths.forEach((item, index) => {
  367. compressImgs.push(new Promise((resolve, reject) => {
  368. // #ifndef H5
  369. uni.compressImage({
  370. src: item,
  371. quality: this.quality,
  372. success: res => {
  373. //console.log('compressImage', res.tempFilePath)
  374. results.push(res.tempFilePath);
  375. resolve(res.tempFilePath);
  376. },
  377. fail: (err) => {
  378. //console.log(err.errMsg);
  379. reject(err);
  380. },
  381. complete: () => {
  382. //uni.hideLoading();
  383. }
  384. })
  385. // #endif
  386. // #ifdef H5
  387. this.canvasDataURL(item, {
  388. quality: this.quality / 100
  389. }, (base64Codes) => {
  390. //this.imgUpload(base64Codes);
  391. results.push(base64Codes);
  392. resolve(base64Codes);
  393. })
  394. // #endif
  395. }))
  396. })
  397. Promise.all(compressImgs) //执行所有需请求的接口
  398. .then((results) => {
  399. uni.hideLoading();
  400. console.log('imgUpload', results)
  401. this.imgUpload(results);
  402. })
  403. .catch((res, object) => {
  404. uni.hideLoading();
  405. });
  406. },
  407. imgUpload(tempFilePaths) {
  408. // if (this.action == '') {
  409. // uni.showToast({
  410. // title: '未配置上传地址',
  411. // icon: 'none',
  412. // duration: 2000
  413. // });
  414. // return false;
  415. // }
  416. uni.showLoading({
  417. title: '上传中'
  418. });
  419. console.log('imgUpload', tempFilePaths)
  420. let uploadImgs = [];
  421. tempFilePaths.forEach((item, index) => {
  422. uploadImgs.push(new Promise((resolve, reject) => {
  423. // console.log(index, item)
  424. const uploadTask = uni.uploadFile({
  425. url: this.action, //仅为示例,非真实的接口地址
  426. filePath: item,
  427. name: this.name,
  428. // fileType: 'image',
  429. formData: this.formData,
  430. header: this.headers,
  431. success: (uploadFileRes) => {
  432. //uni.hideLoading();
  433. //console.log(typeof this.uploadSuccess)
  434. //console.log('')
  435. if (typeof this.uploadSuccess == 'function') {
  436. if (this.uploadSuccess(uploadFileRes).success) {
  437. this.value.push(this.uploadSuccess(uploadFileRes)
  438. .url)
  439. this.$emit("input", this.uploadLists);
  440. }
  441. }
  442. resolve(uploadFileRes);
  443. this.$emit("uploadSuccess", uploadFileRes);
  444. },
  445. fail: (err) => {
  446. console.log(err);
  447. //uni.hideLoading();
  448. reject(err);
  449. this.$emit("uploadFail", err);
  450. },
  451. complete: () => {
  452. //uni.hideLoading();
  453. }
  454. });
  455. }))
  456. })
  457. Promise.all(uploadImgs) //执行所有需请求的接口
  458. .then((results) => {
  459. uni.hideLoading();
  460. })
  461. .catch((res, object) => {
  462. uni.hideLoading();
  463. this.$emit("uploadFail", res);
  464. });
  465. // uploadTask.onProgressUpdate((res) => {
  466. // //console.log('',)
  467. // uni.showLoading({
  468. // title: '上传中' + res.progress + '%'
  469. // });
  470. // if (res.progress == 100) {
  471. // uni.hideLoading();
  472. // }
  473. // });
  474. },
  475. canvasDataURL(path, obj, callback) {
  476. var img = new Image();
  477. img.src = path;
  478. img.onload = function() {
  479. var that = this;
  480. // 默认按比例压缩
  481. var w = that.width,
  482. h = that.height,
  483. scale = w / h;
  484. w = obj.width || w;
  485. h = obj.height || (w / scale);
  486. var quality = 0.8; // 默认图片质量为0.8
  487. //生成canvas
  488. var canvas = document.createElement('canvas');
  489. var ctx = canvas.getContext('2d');
  490. // 创建属性节点
  491. var anw = document.createAttribute("width");
  492. anw.nodeValue = w;
  493. var anh = document.createAttribute("height");
  494. anh.nodeValue = h;
  495. canvas.setAttributeNode(anw);
  496. canvas.setAttributeNode(anh);
  497. ctx.drawImage(that, 0, 0, w, h);
  498. // 图像质量
  499. if (obj.quality && obj.quality <= 1 && obj.quality > 0) {
  500. quality = obj.quality;
  501. }
  502. // quality值越小,所绘制出的图像越模糊
  503. var base64 = canvas.toDataURL('image/jpeg', quality);
  504. // 回调函数返回base64的值
  505. callback(base64);
  506. }
  507. },
  508. }
  509. }
  510. </script>
  511. <style lang="scss" scoped>
  512. .preview-full {
  513. position: fixed;
  514. top: 0;
  515. left: 0;
  516. bottom: 0;
  517. width: 100%;
  518. height: 100%;
  519. z-index: 1002;
  520. }
  521. .preview-full {
  522. video {
  523. width: 100%;
  524. height: 100%;
  525. z-index: 1002;
  526. }
  527. }
  528. .preview-full-close {
  529. position: fixed;
  530. right: 32rpx;
  531. top: 150rpx;
  532. width: 80rpx;
  533. height: 80rpx;
  534. line-height: 60rpx;
  535. text-align: center;
  536. z-index: 29099;
  537. color: #fff;
  538. font-size: 65rpx;
  539. font-weight: bold;
  540. // text-shadow: 1px 2px 5px rgb(0 0 0);
  541. }
  542. .xfx-image-upload-list {
  543. display: flex;
  544. flex-wrap: wrap;
  545. }
  546. .xfx-image-upload-Item {
  547. margin-right: 45rpx;
  548. position: relative;
  549. margin-bottom: 30rpx;
  550. width: 184rpx;
  551. height: 186rpx;
  552. border-radius: 14rpx;
  553. border: 1rpx solid #E6E6E6;
  554. background: #F7F8FA;
  555. }
  556. .xfx-image-upload-Item:nth-child(3n) {
  557. margin-right: 0;
  558. }
  559. .xfx-image-upload-Item {
  560. image {
  561. width: 100%;
  562. height: 100%;
  563. border-radius: 14rpx;
  564. }
  565. }
  566. .xfx-image-upload-Item-video {
  567. width: 100%;
  568. height: 100%;
  569. border-radius: 14rpx;
  570. position: relative;
  571. image {
  572. width: 184rpx;
  573. height: 186rpx;
  574. border-radius: 14rpx;
  575. }
  576. }
  577. .xfx-image-upload-Item-video-fixed {
  578. position: absolute;
  579. top: 0;
  580. left: 0;
  581. bottom: 0;
  582. width: 100%;
  583. height: 100%;
  584. border-radius: 12rpx;
  585. display: flex;
  586. justify-content: center;
  587. align-items: center;
  588. z-index: 50;
  589. }
  590. .xfx-image-upload-Item-video-fixed {
  591. .playBtn {
  592. width: 50rpx;
  593. height: 50rpx;
  594. }
  595. }
  596. .xfx-image-upload-Item {
  597. video {
  598. width: 100%;
  599. height: 100%;
  600. border-radius: 10rpx;
  601. }
  602. }
  603. .xfx-image-upload-Item-add {
  604. padding-top: 56rpx;
  605. box-sizing: border-box;
  606. text-align: center;
  607. background-color: #F6F7F8;
  608. display: flex;
  609. flex-direction: column;
  610. align-items: center;
  611. }
  612. .text-upload {
  613. font-size: 24rpx;
  614. line-height: 33rpx;
  615. color: #A4A8AC;
  616. display: block;
  617. margin-top: 16rpx;
  618. }
  619. .xfx-image-upload-Item-del {
  620. border-radius: 50%;
  621. font-size: 30rpx;
  622. position: absolute;
  623. width: 36rpx;
  624. height: 36rpx;
  625. line-height: 36rpx;
  626. text-align: center;
  627. top: 4rpx;
  628. right: 8rpx;
  629. z-index: 51;
  630. display: flex;
  631. justify-content: center;
  632. align-items: center;
  633. background: rgba(0, 0, 0, .41);
  634. color: rgba(255, 255, 255, 1);
  635. }
  636. .xfx-image-upload-Item-del-cover {
  637. border-radius: 50%;
  638. background-color: #F13629;
  639. font-size: 24rpx;
  640. position: absolute;
  641. width: 36rpx;
  642. height: 36rpx;
  643. text-align: center;
  644. top: -18rpx;
  645. right: -18rpx;
  646. color: #fff;
  647. /* #ifdef APP-PLUS */
  648. line-height: 25rpx;
  649. /* #endif */
  650. /* #ifndef APP-PLUS */
  651. line-height: 35rpx;
  652. /* #endif */
  653. z-index: 51;
  654. }
  655. </style>