share.vue 17 KB

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