前言:项目中有一个分享商品海报需求,分别为h5端和app端(其中app端)是使用uniapp来进行开发的,其中h5端是使用vue-canvas-poster插件来进行开发的,app是使用uniapp插件市场的l-painter插件进行开发的,接下来我们具体看下它们的使用方法

App端

项目原来具有分享到微信等供应商的,使用了uni.getProvider(OBJECT)方法获取供应商并且分享

如图点击右上角分享按钮下面会弹出分享的供应商(这是项目原本自带的功能),同时我的需求需要再此基础上再次弹出海报

实现步骤

1.首先在项目中导入插件l-painter,插件地址海报画板 - DCloud 插件市场大家可自行获取

2.我的是做法是把分享的海报封装成一个组件,具体代码如下

<template>
  <view v-if="showShare">
    <!-- 遮罩放在最外侧,遮罩层级低于分享图,高于商品详情页面 -->
    <view class="maskBg"></view>
    <view class="talent_poster">
      <div style="position:relative;">
        <div class="closeBtn" @click="showShare = false" v-show="operateIcon">X</div>
        <div>
          <image :src="path" mode="widthFix"></image>
        </div>
        <div class="download" @click="download" v-show="operateIcon">
          <image style="width: 40rpx;" src="../../static/download.png" mode="widthFix"></image>
        </div>
      </div>
      <l-painter :board="poster" isCanvasToTempFilePath @success="path = $event" hidden />
    </view>
  </view>
</template>

<script>
export default {
  props: {
    goodsInfo: {
      type: Object,
      default: () => {}
    },
    qucodeUrl:{
       type: String,
       default: ''
    }
  },
  data() {
    return {
      path: '',
      operateIcon:false,
      showShare: false,
      poster: {
        css: {
          width: '750rpx',
          marginTop: '40rpx',
          marginBottom: '40rpx',
          background: '#fff',
          borderRadius: '20px'
        },
        views: [
          {
            css: {
              marginLeft: '40rpx',
              marginTop: '30rpx',
              padding: '26rpx',
              boxSizing: 'border-box',
              background: '#fff',
              borderRadius: '16rpx',
              width: '670rpx'
              //   boxShadow: '0 20rpx 58rpx rgba(0,0,0,.15)'
            },
            views: [
              {
                src: 'https://m.360buyimg.com/babel/jfs/t1/196317/32/13733/288158/60f4ea39E6fb378ed/d69205b1a8ed3c97.jpg',
                type: 'image',
                css: {
                  objectFit: 'cover',
                  objectPosition: '50% 50%',
                  width: '606rpx',
                  height: '606rpx'
                }
              },
              {
                css: {
                  marginTop: '32rpx',
                  color: '#FF0000',
                  fontWeight: 'bold',
                  fontSize: '28rpx',
                  lineHeight: '1em'
                },
                views: [
                  {
                    text: '¥',
                    type: 'text',
                    css: {
                      verticalAlign: 'bottom'
                    }
                  },
                  {
                    text: '39',
                    type: 'text',
                    css: {
                      verticalAlign: 'bottom',
                      fontSize: '60rpx'
                    }
                  },
                  {
                    text: '¥59.99',
                    type: 'text',
                    css: {
                      verticalAlign: 'bottom',
                      paddingLeft: '10rpx',
                      fontWeight: 'normal',
                      textDecoration: 'line-through',
                      color: '#cacaca',
                      fontSize: '36rpx'
                    }
                  }
                ],

                type: 'view'
              },
              {
                css: {
                  marginTop: '32rpx',
                  fontSize: '26rpx',
                  color: '#8c5400'
                },
                views: [
                  {
                    text: '肖富贵卓越系列',
                    type: 'text',
                    css: {
                      fontSize: '28rpx',
                      color: '#3D3D3D'
                    }
                  }
                ],

                type: 'view'
              },
              {
                css: {
                  //   marginTop: '20rpx',
                  fontSize: '26rpx',
                  color: '#8c5400'
                },
                views: [
                  {
                    text: '.................................................................................................',
                    type: 'text',
                    css: {
                      fontSize: '28rpx',
                      color: '#D8D8D8'
                    }
                  }
                ],

                type: 'view'
              },
              {
                css: {
                  marginTop: '20rpx'
                },
                views: [
                  {
                    src: 'https://m.360buyimg.com/babel/jfs/t1/196317/32/13733/288158/60f4ea39E6fb378ed/d69205b1a8ed3c97.jpg',
                    type: 'image',
                    css: {
                      width: '200rpx',
                      height: '200rpx',
                      color: '#3D3D3D',
                      fontSize: '24rpx',
                      alignSelf: 'flex'
                    }
                  },
                  {
                    text: '1.保存图片到相册',
                    type: 'text',
                    css: {
                      paddingLeft: '20rpx',
                      paddingTop: '35rpx'
                    }
                  },
                  {
                    text: '2.图片分享给好友',
                    type: 'text',
                    css: {
                      paddingLeft: '220rpx',
                      position: 'absolute',
                      top: '40%'
                    }
                  },
                  {
                    text: '3.好友长按识别二维码可见商品详情',
                    type: 'text',
                    css: {
                      paddingLeft: '220rpx',
                      position: 'absolute',
                      top: '60%'
                    }
                  }
                ],
                type: 'view'
              }
            ],
            type: 'view'
          }
        ]
      }
    };
  },
  methods: {
    open() {
      this.showShare = true;
    //   console.log( this.qucodeUrl,'195');
    //   console.log( this.poster.views[0]['views'][4]['views'][0],'196*');
      this.poster.views[0]['views'][0].src = this.goodsInfo.goods_img;
      this.poster.views[0]['views'][1]['views'][1].text =  this.goodsInfo.shop_price
      this.poster.views[0]['views'][1]['views'][2].text = '¥' + this.goodsInfo.market_price
      this.poster.views[0]['views'][2]['views'][0].text =  this.goodsInfo.goods_name
      this.poster.views[0]['views'][4]['views'][0].src = this.qucodeUrl
      setTimeout(() => {
        this.operateIcon = true
      }, 2000);
    },
    download() {
      uni.saveImageToPhotosAlbum({
        filePath: this.path,
        success() {
          uni.showToast({
            title: '保存成功',
            icon: 'success'
          });
        },
        fail() {
          uni.showToast({
            title: '保存失败',
            icon: 'none'
          });
        }
      });
    }
  }
};
</script>

<style lang="scss" scoped>
.talent_poster {
  position: fixed;
  width: 80%;
  top: 50%;
  left: 50%;
  z-index: 999;
  transform: translate(-50%, -50%);
  margin: 0 auto;

  img {
    width: 100%;
    height: 100%;
    margin-bottom: 10px;
  }
}
.maskBg {
  position: fixed;
  width: 100%;
  height: 100vh;
  background: rgba($color: #000000, $alpha: 0.3);
  z-index: 998;
  left: 0;
  top: 0;
}
.closeBtn {
  position: absolute;
  right: -7px;
  z-index: 99;
  top: -9px;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background-color: #959590;
  text-align: center;
  line-height: 20px;
  color: white;
}
.download {
  position: absolute;
  bottom: 80px;
  right: 0;
  z-index: 1000;
}
</style>

我是使用文档中的json方法进行实现的,具体实例可参照文档

其中配置共分为四种类型:

  1. views:全局生成海报的最外层盒子
  2. text:海报需要文本使用该类型进行声明
  3. image:海报中使用图片使用该类型进行声明
  4. qrcode:海报中使用二维码需要使用该类型进行声明

其他详细的配置以及css样式具体查看官方文档进行描

3.我调用分享组件打开页面的初次效果

初版的海报就生成成功了,海报中的内容可以根据接口获取自己的数据进行替换,我的是在opne方法中尽心替换的

3.组件上的功能注意点,遮罩层的层级问题

 4.遇到的问题,我们点击海报右上角的关闭按钮,需要点击两次才可以进行关闭

从图片我们可以看到全屏有一层暗灰色的遮罩,点击海报的x按钮,第一次海报没有关闭,但是下面的

供应商列表关闭了

5.问题所在:从上面的现象我们不难看出,出现问题的原因就是,供应商弹窗会带一个层级最高的遮罩在最外层,所以我们在海报上第一次点击x按钮,点击的是供应商列表的遮罩,所以会把供应商列表关闭

6.需要实现的效果:点击x关闭海报,但是不需要把供应商列表关闭掉,可能用户还需要分享到微信等其他方式也不一定

7.解决方案:由于供应商列表组件封装的遮罩是全屏的,并且点击屏幕任何地方都会将其关闭,于是我们找到遮罩层的文件,把他的遮罩高度从原来的100%,设置成0,这样就不会不经意触发遮罩进行关闭供应商列表了,在有需要关闭供应商的地方,我们使用js方法进行手动关闭即可

这样就可以解决这个问题

如下是供应商遮罩生成的代码,使用的时候调用该方法,也可以具体查看uni-share - DCloud 插件市场官方文档

function shareFn(shareInfo,drawList,callback){
	//以下为计算菜单的nview绘制布局,为固定算法,使用者无关关心
	let screenWidth = plus.screen.resolutionWidth
	//以360px宽度屏幕为例,上下左右边距及2排按钮边距留25像素,图标宽度55像素,同行图标间的间距在360宽的屏幕是30px,但需要动态计算,以此原则计算4列图标分别的left位置
	//图标下的按钮文字距离图标5像素,文字大小12像素
	//底部取消按钮高度固定为44px
	//TODO 未处理横屏和pad,这些情况6个图标应该一排即可
	let marginTop = 25,//上间距
		marginLeft=25,//左间距
		iconWidth = 40,//图标宽宽
		iconHeight= 40,//图标高度
		icontextSpace = 15,//图标与文字间距
		textHeight = 10//文字大小
	let left1 = marginLeft / 360 * screenWidth;
	let colNumber=Math.floor(screenWidth/(iconWidth+marginLeft));
	let i=(screenWidth-(iconWidth+marginLeft)*colNumber-marginLeft)/(colNumber+1);
	let initMargin=marginLeft+i;
	let itemWidth=iconWidth+initMargin;
	let itemHeight=iconHeight+icontextSpace+textHeight+marginTop;
	let textTop=iconHeight+icontextSpace;
	let alphaBg = new plus.nativeObj.View("alphaBg", { //先创建遮罩层
		top: '0px',
		left: '0px',
		height: '0',
		width: '100%',
		backgroundColor: 'rgba(0,0,0,0.5)'
	});
	alphaBg.addEventListener("click", function() { //处理遮罩层点击
		alphaBg.hide();
		shareMenu.hide();
	})
	let shareMenu = new plus.nativeObj.View("shareMenu", { //创建底部图标菜单
		bottom: '0px',
		left: '0px',
		height: Math.ceil(drawList.length/colNumber)*itemHeight+marginTop+44+1+'px',
		width: '100%',
		backgroundColor: 'rgb(255,255,255)'
	});
	//绘制底部图标菜单的内容
	shareMenu.draw([
		{
			tag: 'rect',//菜单顶部的分割灰线
			color: '#e7e7e7',
			position: {
				top: '0px',
				height: '1px'
			}
		},
		{
			tag: 'font',
			id: 'sharecancel',//底部取消按钮的文字
			text: '取消分享',
			textStyles: {
				size: '14px'
			},
			position: {
				bottom: '0px',
				height: '44px'
			}
		},
		{
			tag: 'rect',//底部取消按钮的顶部边线
			color: '#e7e7e7',
			position: {
				bottom: '45px',
				height: '1px'
			}
		}
	]);
	drawList.map((v,k)=>{
		let time=new Date().getTime();
		let row=Math.floor(k/colNumber);
		let col=k%colNumber;
		let item=[{
			src:v.icon,
			id:Math.random()*1000+time,
			tag:"img",
			position:{
				top:row*itemHeight+marginTop,
				left:col*itemWidth+initMargin,
				width:iconWidth,
				height:iconWidth
			}
		},{
			text:v.text,
			id:Math.random()*1000+time,
			tag:"font",
			textStyles:{
				size: textHeight
			},
			position:{
				top:row*itemHeight+textTop,
				left:col*itemWidth+initMargin,
				width:iconWidth,
				height:iconWidth
			}
		}];
		shareMenu.draw(item);
	});
	shareMenu.addEventListener("click", function(e) { //处理底部图标菜单的点击事件,根据点击位置触发不同的逻辑
		if (e.screenY > plus.screen.resolutionHeight - 88) { //点击了底部取消按钮
			alphaBg.hide();
			shareMenu.hide();
		} else if (e.clientX < 5 || e.clientX > screenWidth - 5 || e.clientY < 5) {
			//屏幕左右边缘5像素及菜单顶部5像素不处理点击
		} else { //点击了图标按钮
			let x=e.clientX;
			let y=e.clientY;
			let colIdx=Math.floor(x/itemWidth);
			let rowIdx=Math.floor(y/itemHeight);
			let tapIndex=colIdx+rowIdx*colNumber;
			callback&&callback(tapIndex);
			alphaBg.hide();
			shareMenu.hide();
		}
	})
	return {alphaBg,shareMenu};
};
export default shareFn;

 8.最后就是根据接口动态赋值海报上的内容了

直接给json里面的文件动态赋值就行了 

h5端 

  1. 下载vue-canvas-poster
npm i vue-canvas-poster --save

     2.在main.js 中引入


import VueCanvasPoster from 'vue-canvas-poster'
Vue.use(VueCanvasPoster);

     3.封装成组件使用

<template>
  <div v-if="shareStatus">
    <div class="maskBg"></div>
    <div class="talent_poster">
        <!-- :widthPixels="1000" 一般不要设置最大值,如果设置了最大值,海报中图片过大,可能回出现重叠-->
        <div style="height:1px;overflow:hidden">
        <vue-canvas-poster
            :painting="painting"
            @success="canvasSuccess"
            @fail="canvasFail"
        ></vue-canvas-poster>
      </div>
      <div style="position:relative;">
        <div class="closeBtn" @click="shareStatus = false">X</div>
        <div><img :src="posterImg" /></div>
      </div>
    </div>
  </div>
</template>
<script>
import {
  Toast
} from 'vant';
export default {
  middleware: '',
  props:{
    goodsInfo:{
      type:Object,
      default:()=>{}
    },
    shareUrl:{
      type:String,
      default:''
    }
  },
  components:{
    [Toast.name]: Toast
  },
  data() {
    return {
      shareStatus:false,
      posterImg: '', //生成的海报
      painting: {
        width: '750px',
        height: '1168px',
        background: '#fff',
        borderRadius:'20px',
        views: [
          // 二维码
          {
            type: 'qrcode',
            content: '',
            background: '#000000',
            css: {
              color: '#000',
              background: '#f5f5f5',
              width: '0px',
              height: '0px',
              borderRadius: '10px',
              borderColor: '#000000'
            }
          },
          {
            type: 'image',
            url: 'https://fastly.jsdelivr.net/npm/@vant/assets/cat.jpeg',
            text: '',
            css: {
              top: '60px',
              right: '10%',
              width: '600px',
              height: '600px',
              maxLines: 1,
              fontSize: '70px',
              color: '#0068B7'
            }
          },
          {
            type: 'text',
            text: '',
            css: {
              top: '58%',
              left: '3%',
              width: '300px',
              maxLines: 1,
              textAlign: 'left',
              fontSize: '60px',
              color: '#FF0000'
            }
          },
          {
            type: 'text',
            text: '',
            css: {
              top: '60%',
              left: '40%',
              width: '300px',
              maxLines: 1,
              textAlign: 'left',
              fontSize: '36px',
              color: '#CACACA',
              textDecoration:'line-through'
            }
          },
          {
            type: 'text',
            text: '',
            css: {
              top: '65%',
              left: '6%',
              width: '100%',
              maxLines: 1,
              textAlign: 'left',
              fontSize: '28px',
              color: '#3D3D3D'

            }
          },
          {
            type: 'text',
            text: '.......................................................................................................',
            css: {
              top: '70%',
              left: '0',
              width: '100%',
              maxLines: 1,
              textAlign: 'center',
              fontSize: '28px',
              color: '#D8D8D8'

            }
          },
          {
            type: 'image',
            url: 'https://fastly.jsdelivr.net/npm/@vant/assets/cat.jpeg',
            text: '',
            css: {
              top: '78%',
              left: '5%',
              width: '200px',
              height: '200px',
              maxLines: 1,
              fontSize: '70px',
              color: '#0068B7'
            }
          },
          {
            type: 'text',
            text: '1.保存图片到相册',
            css: {
              top: '80%',
              left: '0',
              width: '100%',
              maxLines: 1,
              textAlign: 'center',
              fontSize: '24px',
              color: '#3D3D3D'

            }
          },
          {
            type: 'text',
            text: '2.图片分享给好友',
            css: {
              top: '84%',
              left: '0',
              width: '100%',
              maxLines: 1,
              textAlign: 'center',
              fontSize: '24px',
              color: '#3D3D3D'

            }
          },
          {
            type: 'text',
            text: '3.好友长按识别二维码可见商品详情',
            css: {
              top: '88%',
              left: '12.5%',
              width: '100%',
              maxLines: 1,
              textAlign: 'center',
              fontSize: '24px',
              color: '#3D3D3D'
            }
          }
        ]
      }
    };
  },
  methods: {
    canvasSuccess(src) {
      this.posterImg = src;
      setTimeout(() => {
        if (this.posterImg !=='') {
          this.open();
          this.shareStatus = true;
        }
      }, 2000);
    },
    canvasFail(err) {
      alert(err);
    },
    open() {
      this.painting.views[1].url = this.goodsInfo.goods_img;
      if (this.goodsInfo.shop_price.length<9) {
        this.painting.views[3].css.left = '42%';
      }
      if (this.goodsInfo.shop_price.length<6) {
        this.painting.views[3].css.left = '32%';
      }
      this.painting.views[2].text = '¥'+this.goodsInfo.shop_price;
      this.painting.views[3].text = '¥'+this.goodsInfo.market_price;
      this.painting.views[4].text = this.goodsInfo.goods_name;
      this.painting.views[6].url = this.shareUrl;
      // console.log(this.shareUrl,'111');
    }
  }
};
</script>
<style lang="scss" scoped>
.talent_poster {
    position: fixed;
    width: 80%;
    top: 50%;
    left: 50%;
    z-index: 999;
    transform: translate(-50%, -50%);
    margin: 0 auto;
    img {
        width: 100%;
        height: 100%;
        margin-bottom: 10px;
    }
}
.closeBtn {
  position: absolute;
    right: -7px;
    z-index: 99;
    top: -9px;
    width: 20px;
    height: 20px;
    border-radius: 50%;
    background-color: #959590;
    text-align: center;
    line-height: 20px;
    color: white;
}
.maskBg {
  position:fixed;
  width: 100%;
  height: 100vh;
  background: rgba($color: #000000, $alpha: 0.3);
  top:0;
  z-index:99
}
</style>

4.注意点

  • 总体使用和uniapp的l-painter类似,其中海报内容数据都是在data中配置json数据
  • 也是根据type配置类型,分别是views,qrcode,text,image
  • 海报数据动态赋值也是需要修改json中默认的配置数据

5.完成效果图

GitHub 加速计划 / vu / vue
207.54 K
33.66 K
下载
vuejs/vue: 是一个用于构建用户界面的 JavaScript 框架,具有简洁的语法和丰富的组件库,可以用于开发单页面应用程序和多页面应用程序。
最近提交(Master分支:2 个月前 )
73486cb5 * chore: fix link broken Signed-off-by: snoppy <michaleli@foxmail.com> * Update packages/template-compiler/README.md [skip ci] --------- Signed-off-by: snoppy <michaleli@foxmail.com> Co-authored-by: Eduardo San Martin Morote <posva@users.noreply.github.com> 4 个月前
e428d891 Updated Browser Compatibility reference. The previous currently returns HTTP 404. 5 个月前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐