share.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597
  1. <template name="share">
  2. <view class="poster_page">
  3. <nav-bar title="海报"></nav-bar>
  4. <canvas canvas-id="poster" class="poster_canvas"></canvas>
  5. <swiper class="poster_swiper" previous-margin="110rpx" circular :current="swiperIndex" next-margin="110rpx" @change="onSwiperChange">
  6. <swiper-item v-for="(item, index) of dataInfo.headImgs" :key="index">
  7. <view class="goods_info_box" :class="{ active: swiperIndex == index }">
  8. <image class="goods_image" :src="item" mode="aspectFit"></image>
  9. <view class="goods_info">
  10. <view class="goods_name">{{dataInfo.goodsRecommendShare.goodsName}}</view>
  11. <view class="price_box">
  12. <view class="price">{{dataInfo.goodsRecommendShare.goodsPrice}}</view>
  13. <view class="store_price">{{dataInfo.priceShop}}</view>
  14. </view>
  15. <view class="poster_info">
  16. <view class="info">
  17. <view>长按识别二维码访问</view>
  18. <text>{{platformName}}</text>
  19. </view>
  20. <image class="poster_code_image" :src="dataInfo.goodsRecommendShare.recommendCodeGoods" mode="aspectFit"></image>
  21. </view>
  22. </view>
  23. </view>
  24. </swiper-item>
  25. </swiper>
  26. <view class="share_save_box">
  27. <!-- #ifdef MP -->
  28. <button open-type="share">
  29. <image src="@/static/demo/ic_pic.png" mode="aspectFit"></image>
  30. <text>发给好友</text>
  31. </button>
  32. <!-- #endif -->
  33. <!-- #ifdef APP-PLUS -->
  34. <button @click="onAppShare">
  35. <image src="@/static/demo/ic_pic.png" mode="aspectFit"></image>
  36. <text>发给好友</text>
  37. </button>
  38. <!-- #endif -->
  39. <button class="onSave" @click="onSaveImg">
  40. <image src="@/static/demo/ic_weixin.png" mode="aspectFit"></image>
  41. <text>保存图片</text>
  42. </button>
  43. </view>
  44. <!-- #ifdef H5 -->
  45. <view class="h5_press_save" v-if="h5SaveImg" @click="h5SaveImg = ''">
  46. <image :src="h5SaveImg" mode="widthFix"></image>
  47. <button class="download">长按图片保存图片</button>
  48. </view>
  49. <!-- #endif -->
  50. </view>
  51. </template>
  52. <script>
  53. import { mapState, mapMutations } from 'vuex';
  54. // 文字换行
  55. function drawtext(text, maxWidth) {
  56. let textArr = text.split("");
  57. let len = textArr.length;
  58. // 上个节点
  59. let previousNode = 0;
  60. // 记录节点宽度
  61. let nodeWidth = 0;
  62. // 文本换行数组
  63. let rowText = [];
  64. // 如果是字母,侧保存长度
  65. let letterWidth = 0;
  66. // 汉字宽度
  67. let chineseWidth = 21;
  68. // otherFont宽度
  69. let otherWidth = 10.5;
  70. for (let i = 0; i < len; i++) {
  71. if (/[\u4e00-\u9fa5]|[\uFE30-\uFFA0]/g.test(textArr[i])) {
  72. if(letterWidth > 0){
  73. if(nodeWidth + chineseWidth + letterWidth * otherWidth > maxWidth){
  74. rowText.push({
  75. type: "text",
  76. content: text.substring(previousNode, i)
  77. });
  78. previousNode = i;
  79. nodeWidth = chineseWidth;
  80. letterWidth = 0;
  81. } else {
  82. nodeWidth += chineseWidth + letterWidth * otherWidth;
  83. letterWidth = 0;
  84. }
  85. } else {
  86. if(nodeWidth + chineseWidth > maxWidth){
  87. rowText.push({
  88. type: "text",
  89. content: text.substring(previousNode, i)
  90. });
  91. previousNode = i;
  92. nodeWidth = chineseWidth;
  93. }else{
  94. nodeWidth += chineseWidth;
  95. }
  96. }
  97. } else {
  98. if(/\n/g.test(textArr[i])){
  99. rowText.push({
  100. type: "break",
  101. content: text.substring(previousNode, i)
  102. });
  103. previousNode = i + 1;
  104. nodeWidth = 0;
  105. letterWidth = 0;
  106. }else if(textArr[i] == "\\" && textArr[i + 1] == "n"){
  107. rowText.push({
  108. type: "break",
  109. content: text.substring(previousNode, i)
  110. });
  111. previousNode = i + 2;
  112. nodeWidth = 0;
  113. letterWidth = 0;
  114. }else if(/[a-zA-Z0-9]/g.test(textArr[i])){
  115. letterWidth += 1;
  116. if(nodeWidth + letterWidth * otherWidth > maxWidth){
  117. rowText.push({
  118. type: "text",
  119. content: text.substring(previousNode, i + 1 - letterWidth)
  120. });
  121. previousNode = i + 1 - letterWidth;
  122. nodeWidth = letterWidth * otherWidth;
  123. letterWidth = 0;
  124. }
  125. } else{
  126. if(nodeWidth + otherWidth > maxWidth){
  127. rowText.push({
  128. type: "text",
  129. content: text.substring(previousNode, i)
  130. });
  131. previousNode = i;
  132. nodeWidth = otherWidth;
  133. }else{
  134. nodeWidth += otherWidth;
  135. }
  136. }
  137. }
  138. }
  139. if (previousNode < len) {
  140. rowText.push({
  141. type: "text",
  142. content: text.substring(previousNode, len)
  143. });
  144. }
  145. return rowText;
  146. }
  147. let settingWritePhotosAlbum = false;
  148. export default {
  149. data() {
  150. return {
  151. swiperIndex: 0,
  152. posterImgs: [],
  153. dataInfo: {
  154. priceShop: "¥699.0",
  155. headImgs: [
  156. "http://qn.kemean.cn/file/upload/202005/21/1590043402088i2jxb79n.jpg?imageView2/0/w/800",
  157. "http://qn.kemean.cn/file/upload/202005/21/1590043404759qml3zmlm.jpg?imageView2/0/w/800",
  158. "http://qn.kemean.cn/file/upload/202005/21/1590043407501c6o63bmi.jpg?imageView2/0/w/800",
  159. "http://qn.kemean.cn/file/upload/202005/21/1590043410966jwbtkyw1.jpg?imageView2/0/w/800",
  160. "http://qn.kemean.cn/file/upload/202005/21/1590043413622bnysmgy9.jpg?imageView2/0/w/800"
  161. ],
  162. goodsRecommendShare: {
  163. goodsImg: "https://qn.kemean.cn/file/upload/202005/21/1590043402088i2jxb79n.jpg?imageView2/0/w/800",
  164. goodsName: "冰希黎巴黎红精粹沙龙香水50ml",
  165. goodsPrice: "¥699.0",
  166. mainLogo: "https://qn.kemean.cn//file/upload/202005/22/1590138818080cmvg4qnl.png",
  167. recommendCodeGoods: "https://qn.kemean.cn/upload/202007/03/9c6a38df800e46bbba6aede3d84c3427"
  168. },
  169. share: {
  170. shareContent: "商家云系统,点击了解",
  171. shareImg: "http://qn.kemean.cn/file/upload/202005/21/1590043402088i2jxb79n.jpg?imageView2/0/w/800",
  172. shareTitle: "冰希黎巴黎红精粹沙龙香水50ml",
  173. shareUrl: "http://kemean.com/download/3jiayunbz/index.htmlpages/mall/shopPage/goodsDetail?objId=18111505&recommendCode=32029043"
  174. }
  175. },
  176. platformName: "",
  177. h5SaveImg: ""
  178. };
  179. },
  180. //第一次加载
  181. onLoad(e) {
  182. this.platformName = this.$base.platformName;
  183. if (e.objId) {
  184. this.objId = e.objId;
  185. }
  186. },
  187. computed: {
  188. ...mapState(["userInfo"])
  189. },
  190. //方法
  191. methods: {
  192. // 轮播图变化
  193. onSwiperChange(e) {
  194. this.swiperIndex = e.detail.current;
  195. },
  196. // 创建海报
  197. createPoster() {
  198. return new Promise((resolve, reject) => {
  199. uni.showLoading({
  200. title: '海报生成中'
  201. });
  202. const ctx = uni.createCanvasContext('poster');
  203. ctx.fillRect(0, 0, 375, 673);
  204. ctx.setFillStyle("#FFF");
  205. ctx.fillRect(0, 0, 375, 673);
  206. uni.downloadFile({
  207. url: this.dataInfo.headImgs[this.swiperIndex],
  208. success: (res) => {
  209. if (res.statusCode === 200) {
  210. ctx.drawImage(res.tempFilePath, 0, 0, 375, 375);
  211. uni.downloadFile({
  212. url: this.dataInfo.goodsRecommendShare.recommendCodeGoods,
  213. success: (res2) => {
  214. if (res.statusCode === 200) {
  215. // 商品标题
  216. ctx.setFontSize(21);
  217. ctx.setFillStyle('#333');
  218. let drawtextList = drawtext(this.dataInfo.goodsRecommendShare.goodsName, 341);
  219. let textTop = 0;
  220. drawtextList.forEach((item,index) => {
  221. if(index < 2){
  222. textTop = 380 + (index + 1) * 28;
  223. ctx.fillText(item.content, 17, textTop);
  224. }
  225. });
  226. // 商品价格
  227. ctx.setFontSize(26);
  228. ctx.setFillStyle('#f00');
  229. ctx.fillText(this.dataInfo.goodsRecommendShare.goodsPrice, 17, textTop + 47);
  230. // 商品门市价
  231. ctx.setFontSize(18);
  232. ctx.setFillStyle('#999');
  233. let textLeft = 38 + (this.dataInfo.goodsRecommendShare.goodsPrice.length * 13)
  234. ctx.fillText(this.dataInfo.priceShop, textLeft, textTop + 45);
  235. // 商品门市价横线
  236. ctx.beginPath();
  237. ctx.setLineWidth(1);
  238. ctx.moveTo(textLeft - 1, textTop + 38);
  239. ctx.lineTo((textLeft + 5 + this.dataInfo.priceShop.length * 9), textTop + 38);
  240. ctx.setStrokeStyle('#999');
  241. ctx.stroke();
  242. // 商品分割线
  243. ctx.beginPath();
  244. ctx.setLineWidth(1);
  245. ctx.moveTo(17, textTop + 70);
  246. ctx.lineTo(358, textTop + 70);
  247. ctx.setStrokeStyle('#eee');
  248. ctx.stroke();
  249. // 长按识别二维码访问
  250. ctx.setFontSize(19);
  251. ctx.setFillStyle('#333');
  252. ctx.fillText("长按识别二维码访问", 17, textTop + 136);
  253. // 平台名称
  254. ctx.setFontSize(16);
  255. ctx.setFillStyle('#999');
  256. ctx.fillText(this.platformName, 17, textTop + 170);
  257. // 二维码
  258. ctx.drawImage(res2.tempFilePath, 238, textTop + 88, 120, 120);
  259. ctx.draw(true, () => {
  260. // canvas画布转成图片并返回图片地址
  261. uni.canvasToTempFilePath({
  262. canvasId: 'poster',
  263. width: 375,
  264. height: 673,
  265. success: (res) => {
  266. if(this.posterImgs[this.swiperIndex]){
  267. this.posterImgs[this.swiperIndex].temporary = res.tempFilePath;
  268. }else{
  269. this.posterImgs[this.swiperIndex] = {
  270. temporary: res.tempFilePath
  271. };
  272. }
  273. console.log("海报制作成功!");
  274. resolve(res.tempFilePath);
  275. },
  276. fail: () => {
  277. uni.hideLoading();
  278. reject();
  279. }
  280. })
  281. });
  282. } else {
  283. uni.hideLoading();
  284. uni.showToast({
  285. title: '海报制作失败,图片下载失败',
  286. icon: 'none'
  287. });
  288. }
  289. },
  290. fail: err => {
  291. uni.hideLoading();
  292. uni.showToast({
  293. title: '海报制作失败,图片下载失败',
  294. icon: 'none'
  295. });
  296. }
  297. });
  298. } else {
  299. uni.hideLoading();
  300. uni.showToast({
  301. title: '海报制作失败,图片下载失败',
  302. icon: 'none'
  303. });
  304. }
  305. },
  306. fail: err => {
  307. uni.hideLoading();
  308. uni.showToast({
  309. title: '海报制作失败,图片下载失败',
  310. icon: 'none'
  311. });
  312. }
  313. });
  314. });
  315. },
  316. // 保存图片
  317. async onSaveImg() {
  318. let imgUrl = "";
  319. if(this.posterImgs[this.swiperIndex] && this.posterImgs[this.swiperIndex].temporary){
  320. imgUrl = await this.posterImgs[this.swiperIndex].temporary;
  321. }else{
  322. imgUrl = await this.createPoster();
  323. }
  324. // #ifdef H5
  325. this.h5SaveImg = imgUrl;
  326. uni.hideLoading();
  327. // #endif
  328. // #ifdef MP-WEIXIN
  329. uni.showLoading({
  330. title: '海报下载中'
  331. });
  332. if (settingWritePhotosAlbum) {
  333. uni.getSetting({
  334. success: res => {
  335. if (res.authSetting['scope.writePhotosAlbum']) {
  336. uni.saveImageToPhotosAlbum({
  337. filePath: imgUrl,
  338. success: () => {
  339. uni.hideLoading();
  340. uni.showToast({
  341. title: '保存成功'
  342. });
  343. }
  344. });
  345. } else {
  346. uni.showModal({
  347. title: '提示',
  348. content: '请先在设置页面打开“保存相册”使用权限',
  349. confirmText: '去设置',
  350. cancelText: '算了',
  351. success: data => {
  352. if (data.confirm) {
  353. uni.hideLoading();
  354. uni.openSetting();
  355. }
  356. }
  357. });
  358. }
  359. }
  360. });
  361. } else {
  362. settingWritePhotosAlbum = true;
  363. uni.authorize({
  364. scope: 'scope.writePhotosAlbum',
  365. success: () => {
  366. uni.saveImageToPhotosAlbum({
  367. filePath: imgUrl,
  368. success: () => {
  369. uni.hideLoading();
  370. uni.showToast({
  371. title: '保存成功'
  372. });
  373. }
  374. });
  375. }
  376. });
  377. }
  378. // #endif
  379. // #ifdef APP-PLUS
  380. uni.showLoading({
  381. title: '海报下载中'
  382. });
  383. uni.saveImageToPhotosAlbum({
  384. filePath: imgUrl,
  385. success: () => {
  386. uni.hideLoading();
  387. uni.showToast({
  388. title: '保存成功'
  389. });
  390. }
  391. });
  392. // #endif
  393. },
  394. async onAppShare() {
  395. // let imgUrl = "";
  396. // if(this.posterImgs[this.swiperIndex] && this.posterImgs[this.swiperIndex].url){
  397. // imgUrl = this.posterImgs[this.swiperIndex].url;
  398. // } else if(this.posterImgs[this.swiperIndex] && this.posterImgs[this.swiperIndex].temporary){
  399. // let imgData = await this.$http.qnFileUpload([this.posterImgs[this.swiperIndex].temporary]);
  400. // imgUrl = imgData[0];
  401. // }else{
  402. // let data = await this.createPoster();
  403. // let imgData = await this.$http.qnFileUpload([data]);
  404. // imgUrl = imgData[0];
  405. // }
  406. // uni.hideLoading();
  407. uni.share({
  408. provider: 'weixin',
  409. scene: 'WXSceneSession',
  410. type: 0,
  411. title: this.dataInfo.share.shareTitle,
  412. href: this.dataInfo.share.shareUrl,
  413. summary: this.dataInfo.share.shareContent,
  414. imageUrl: this.dataInfo.share.shareImg,
  415. success: function(res) {
  416. console.log('success:' + JSON.stringify(res));
  417. },
  418. fail: function(err) {
  419. console.log('fail:' + JSON.stringify(err));
  420. }
  421. });
  422. }
  423. },
  424. //页面隐藏
  425. onHide() {},
  426. //页面卸载
  427. onUnload() {},
  428. //页面下来刷新
  429. onPullDownRefresh() {},
  430. //页面上拉触底
  431. onReachBottom() {},
  432. //用户点击分享
  433. onShareAppMessage(e) {
  434. let dataInfo = this.dataInfo;
  435. let shareInfo = {
  436. path: "/pages/home/shop/goodsDetail?objId="+dataInfo.objId,
  437. title: dataInfo.share.shareTitle,
  438. imageUrl: dataInfo.share.shareImg
  439. };
  440. if(this.userInfo.token){
  441. shareInfo.path += "&recommendCode=" + this.userInfo.uid;
  442. }
  443. console.log(shareInfo);
  444. return shareInfo;
  445. }
  446. };
  447. </script>
  448. <style lang="scss" scoped>
  449. .poster_page {
  450. min-height: 100vh;
  451. background-color: #f5f5f5;
  452. display: flex;
  453. align-items: center;
  454. }
  455. .poster_canvas {
  456. width: 750rpx;
  457. height: 1334rpx;
  458. position: fixed;
  459. top: -10000rpx;
  460. left: 0rpx;
  461. }
  462. .poster_swiper {
  463. height: 950rpx;
  464. width: 100%;
  465. swiper-item {
  466. box-sizing: border-box;
  467. display: flex;
  468. align-items: center;
  469. .goods_info_box {
  470. width: 100%;
  471. height: 100%;
  472. transform: scale(0.88);
  473. transition: all 0.4s;
  474. position: relative;
  475. overflow: hidden;
  476. background-color: #FFFFFF;
  477. &.active {
  478. transform: scale(1);
  479. }
  480. .goods_image {
  481. width: 100%;
  482. height: calc(100vw - 220rpx);
  483. }
  484. .goods_info {
  485. padding: 24rpx;
  486. .goods_name {
  487. color: #333;
  488. font-size: 30rpx;
  489. }
  490. .price_box {
  491. margin-top: 24rpx;
  492. display: flex;
  493. align-items: center;
  494. .price {
  495. font-size: 38rpx;
  496. color: red;
  497. }
  498. .store_price {
  499. margin-left: 30rpx;
  500. font-size: 26rpx;
  501. color: #999;
  502. text-decoration: line-through;
  503. }
  504. }
  505. .poster_info {
  506. border-top: 2rpx solid #f1f1f1;
  507. padding-top: 24rpx;
  508. margin-top: 24rpx;
  509. display: flex;
  510. align-items: center;
  511. justify-content: space-between;
  512. .info {
  513. display: flex;
  514. flex-direction: column;
  515. view {
  516. color: #333;
  517. font-size: 28rpx;
  518. }
  519. text {
  520. color: #999;
  521. font-size: 24rpx;
  522. margin-top: 20rpx;
  523. }
  524. }
  525. .poster_code_image {
  526. width: 170rpx;
  527. height: 170rpx;
  528. flex-shrink: 0;
  529. }
  530. }
  531. }
  532. }
  533. }
  534. }
  535. .share_save_box {
  536. position: fixed;
  537. bottom: calc((100vh - 950rpx - 240rpx) / 4);
  538. left: 0;
  539. z-index: 6;
  540. width: 100%;
  541. display: flex;
  542. justify-content: space-around;
  543. button {
  544. display: flex;
  545. flex-direction: column;
  546. align-items: center;
  547. background-color: transparent;
  548. image {
  549. width: 60rpx;
  550. height: 60rpx;
  551. }
  552. text {
  553. font-size: 24rpx;
  554. color: #333333;
  555. }
  556. }
  557. }
  558. .h5_press_save {
  559. background-color: #000;
  560. position: fixed;
  561. top: 0;
  562. left: 0;
  563. width: 100%;
  564. height: 100%;
  565. display: flex;
  566. align-items: center;
  567. z-index: 100;
  568. image {
  569. width: 100%;
  570. }
  571. .download {
  572. font-size: 24rpx;
  573. color: #ffffff;
  574. background-color: rgba(0,0,0,0.5);
  575. display: flex;
  576. align-items: center;
  577. flex-direction: row;
  578. justify-content: center;
  579. position: absolute;
  580. padding: 5rpx 30rpx;
  581. border-radius: 40rpx;
  582. bottom: 30rpx;
  583. left: 50%;
  584. transform: translateX(-50%);
  585. &:before {
  586. content: '';
  587. width: 24rpx;
  588. height: 24rpx;
  589. margin-right: 15rpx;
  590. }
  591. }
  592. }
  593. </style>