Case 1
Implementation background:
- The size of the poster is based on the size of the picture, the width is 100%, the height is scaled proportionally, and the QR code is covered on the picture
- If the poster image is not set, use another image, add a bottom fixed image at the bottom, and a QR code, and overlay the log image in the upper left corner of the image
difficulty: - Because it is an image rendered by canvas, but the size of the image is not fixed, that is, the size of the canvas is not fixed (solution: first obtain the size of the image externally, and pass it into this component)
- h5 is rendered normally, but the WeChat applet image cannot be rendered (solution: WeChat does not support canvas to directly render network images, so it needs to be cached locally first)
<template>
<view class="poster-box" ref="posterBox" @click="hidePoster">
<canvas
:style="{
width: posterOptions.poster.canvasWidth + 'px',
height: posterOptions.poster.canvasHeight + 'px',
}"
canvas-id="myCanvas"
></canvas>
<!-- #ifdef MP -->
<image
:src="base64"
class="poster-image"
:draggable="false"
show-menu-by-longpress="true"
></image>
<!-- #endif -->
<!-- #ifdef H5 -->
<image class="poster-image" :src="base64" :draggable="false"></image>
<!-- #endif -->
</view>
</template>
<script>
import Qr from '@/utils/wxqrcode'
export default {
name: 'poster_canvas',
data() {
return {
base64: '', // 海报绘制成图片以便保存
}
},
props: {
//图片地址
posterOptions: {
type: Object,
require: true,
},
},
mounted() {
this.$nextTick(() => {
this.imgToCanvas()
})
},
methods: {
async imgToCanvas() {
let that = this
let canvasHeight = this.posterOptions.isHavePosterImg
? this.posterOptions.poster.canvasHeight
: this.posterOptions.poster.canvasHeight -
this.posterOptions.buttonOption.h
// 渲染大图
let ctx = uni.createCanvasContext('myCanvas', that)
console.log(11111,this.posterOptions)
ctx.drawImage(
this.posterOptions.poster.url,
0,
0,
this.posterOptions.poster.canvasWidth,
canvasHeight
)
if (!this.posterOptions.isHavePosterImg) {
//渲染底部的图片
ctx.drawImage(
this.posterOptions.buttonOption.bottomUrl,
this.posterOptions.buttonOption.x,
this.posterOptions.buttonOption.y,
this.posterOptions.buttonOption.w,
this.posterOptions.buttonOption.h
)
}
// 渲染二维码
if (this.posterOptions.QrOption.isWeChatMiniApp) {
ctx.drawImage(
this.posterOptions.QrOption.QrUrl,
this.posterOptions.QrOption.x,
this.posterOptions.QrOption.y,
this.posterOptions.QrOption.w,
this.posterOptions.QrOption.h
)
} else {
//普通二维码
let qrCodeImg = Qr.createQrCodeImg(this.posterOptions.QrOption.QrUrl, {
size: parseInt(300),
})
ctx.drawImage(
qrCodeImg,
this.posterOptions.QrOption.x,
this.posterOptions.QrOption.y,
this.posterOptions.QrOption.w,
this.posterOptions.QrOption.h
)
}
if (!this.posterOptions.isHavePosterImg) {
// 渲染log 和文字
ctx.drawImage(
this.posterOptions.logOption.logUrl,
this.posterOptions.logOption.x,
this.posterOptions.logOption.y,
this.posterOptions.logOption.w,
this.posterOptions.logOption.h
)
ctx.fillStyle = this.posterOptions.textOption.color
ctx.setFontSize(22)
ctx.font = 'bold arial'
ctx.fillText(
this.posterOptions.textOption.text,
this.posterOptions.textOption.x,
this.posterOptions.textOption.y
)
}
ctx.save() //保存
ctx.draw() //绘制
// 不加延迟的话,base64有时候会赋予undefined
// 把当前画布指定区域的内容导出生成指定大小的图片,并返回文件路径
setTimeout(() => {
uni.canvasToTempFilePath(
{
canvasId: "myCanvas",
fileType: "jpg",
width:that.posterOptions.poster.canvasWidth,
height:that.posterOptions.poster.canvasHeight,
destWidth:that.posterOptions.poster.canvasWidth,
destHeight:that.posterOptions.poster.canvasHeight,
success: function (res) {
that.base64 = res.tempFilePath;
},
fail: function (error) {
console.log(error, "错误");
},
},that
);
}, 500);
},
hidePoster() {
// #ifdef H5
this.$parent.$parent.hidePoster()
// #endif
// #ifndef H5
this.$parent.hidePoster()
// #endif
},
},
}
</script>
<style lang="scss" scoped>
.poster-box {
position: fixed;
top: 0;
z-index: 999;
background-color: rgba(0, 0, 0, 0.8);
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.poster-image {
position: absolute;
top: 0;
left: 0;
z-index: 1000;
width: 100%;
height: 100%;
opacity: 0;
}
</style>
use
<template>
<view
class="contentBgc"
:style="{ backgroundColor: detailObj.backgroundColor || defaultBgc }"
>
<image
class="width-100"
:src="detailObj.activityDetailHeadImg"
mode="widthFix"
/>
<view v-for="item in detailObj.plateDtoList" :key="item.id">
<TopicThree v-if="detailObj.currentTemplate == 3" :itemObj="item" />
<TopicTwo v-if="detailObj.currentTemplate == 2" :itemObj="item" />
<TopicOne v-if="detailObj.currentTemplate == 1" :itemObj="item" />
</view>
<generateaposter v-if="isGetOk" @pChangeType="showPoster" />
<SharePoster v-if="posterShow" :posterOptions="posterOptions"></SharePoster>
</view>
</template>
<script>
import TopicOne from './components/topicOne.vue'
import TopicTwo from './components/topicTwo.vue'
import TopicThree from './components/topicThree.vue'
import { getSpecialTopicData } from '@/api/specialTopic.js'
// #ifdef H5
import { getShopWxConfig, share } from '@/utils/wxShare.js'
// #endif
import mixinWxshare from '@/utils/wxShareTimeline.js' //微信分享朋友圈
import generateaposter from '@/components/gpage/generateaposter.vue'
import SharePoster from './components/sharePoster.vue'
import { cmsWxacodeGetwxacode } from '@/api/product'
export default {
mixins: [mixinWxshare],
components: {
TopicOne,
TopicTwo,
TopicThree,
generateaposter,
SharePoster,
},
onLoad(options) {
this.activityUniqueCode = options.activityUniqueCode
this.initData()
},
data() {
return {
defaultBgc: '#fff', //默认背景色
share: this.$utils.getImgUrl('/static/mcShopImage/share.png'), //分享
detailObj: {},
activityUniqueCode: '',
posterShow: false,
isGetOk: false,
posterOptions: {
isHavePosterImg: false, //用户是否上传了分享海报的图片
activityName: '',
textOption: {
text: '',
x: 0, // X轴坐标
y: 20, // Y轴坐标
color: '#9c6d06',
},
poster: {
url:
'https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fci.xiaohongshu.com%2F0429f29e-85f3-5826-9608-946fc1ee103a%3FimageView2%2F2%2Fw%2F1080%2Fformat%2Fjpg&refer=http%3A%2F%2Fci.xiaohongshu.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1664356680&t=349aa739d52cd748c50b3afd89da2ba2',
canvasWidth: 0,
canvasHeight: 0,
},
QrOption: {
QrUrl: '', // 二维码链接
x: 20, // 图片X轴坐标
y: 20, // 图片Y轴坐标
w: 90, // 图片宽度
h: 90, // 图片高度
isWeChatMiniApp: false, // 是否是微信小程序二维码 true是微信小程序, false 不是微信小程序
},
logOption: {
//log
logUrl:
'https://jemsfile.mochouu.com/provider/91949676/20220831/1661926456418.png',
x: 15, // 图片X轴坐标
y: 15, // 图片Y轴坐标
w: 120, // 图片宽度
h: 45, // 图片高度
},
buttonOption: {
//底部的图片
bottomUrl:
'https://jemsfile.mochouu.com/provider/91949676/20220831/1661930915954.png',
x: 0, // 图片X轴坐标
y: 20, // 图片Y轴坐标
w: 100, // 图片宽度
h: 100, // 图片高度
},
},
screenWidth: 0,
}
},
mounted() {},
// #ifndef H5
/* 微信小程序分享 */
onShareAppMessage: function(res) {
// console.log('分享的userCode:'+this.userInfo.userCode)
let userInfo = this.$utils.getStorageSync('userInfo')
let routes = getCurrentPages() // 获取当前打开过的页面路由数组
let curParam =
routes[routes.length - 1].options ||
routes[routes.length - 1].$route.query //获取路由参数
if (userInfo) {
curParam.userCode = userInfo.userCode
}
let a = this.$utils.pageliks(curParam, routes)
let obj = {
title: this.detailObj.shareTitle || this.detailObj.shareContent || '',
desc:this.detailObj.shareContent||'',
path: a,
imageUrl: this.detailObj.shareImg || this.detailObj.verticalImgUrl || '',
}
return obj
},
// #endif
methods: {
hidePoster() {
this.posterShow = false
},
getWxCode() {
uni.showLoading({
title: '加载中',
})
let that = this
this.isGetOk = false
let routes = getCurrentPages() // 获取当前打开过的页面路由数组
let curRoute = routes[routes.length - 1].route //获取当前页面路由
let curParam = routes[routes.length - 1].options //获取路由参数
let obj = {}
obj.curRoute = curRoute
obj.curParam = curParam
// 拼接参数
let str = ''
for (let key in obj.curParam) {
str += '&' + key + '=' + obj.curParam[key]
}
str = str ? '?' + str.substr(1, str.length) : ''
let data = {
page: '/' + obj.curRoute + str,
}
cmsWxacodeGetwxacode(data).then((res) => {
uni.hideLoading()
if (res.code === 1000) {
this.downImg(res.data, that.posterOptions.QrOption, 'QrUrl')
that.$set(that.posterOptions.QrOption, 'isWeChatMiniApp', true)
that.isGetOk = true
}
})
},
// 显示分享海报
showPoster() {
this.posterShow = true
},
/* 任务分配 */
assignTasks(data) {
/* 设置头部的title */
uni.setNavigationBarTitle({
title: data.activityName,
})
// 初始化分享朋友圈
// #ifndef H5
this.initWxShareData(
this.detailObj.shareTitle || this.detailObj.shareContent,
this.detailObj.shareImg || this.detailObj.verticalImgUrl
)
// #endif
// #ifdef H5
switch (uni.getSystemInfoSync().platform) {
case 'android':
this.fx(1)
break
case 'ios':
this.fx(2)
break
}
// #endif
},
/* 初始化数据 */
initData() {
let that = this
getSpecialTopicData(this.activityUniqueCode).then((res) => {
if (res.code == 1000) {
const { plateDtoList, ...rest } = res.data
let filterData = plateDtoList.map((it) => {
const { productJson, ...re } = it
let objArr = JSON.parse(productJson)
return {
productJson: objArr||[],
...re,
}
})
this.detailObj = { plateDtoList: filterData, ...rest }
this.assignTasks(this.detailObj)
// #ifdef MP
uni.getSystemInfo({
success: (res) => {
that.screenWidth = res.windowWidth
},
})
this.getWxCode()
// #endif
// #ifdef H5
that.screenWidth = document.documentElement.clientWidth
this.posterOptions.QrOption.QrUrl = window.location.href
this.isGetOk = true
// #endif
// 判断是否设置了分享海报的图片
let isHaveImg = this.detailObj.posterImg ? true : false
if(!isHaveImg){
this.downImg(that.posterOptions.logOption.logUrl, that.posterOptions.logOption, 'logUrl')
this.downImg(that.posterOptions.buttonOption.bottomUrl, that.posterOptions.buttonOption, 'bottomUrl')
}
this.$set(this.posterOptions, 'isHavePosterImg', isHaveImg)
this.$set(
this.posterOptions.textOption,
'text',
this.detailObj.activityName
)
// 设置img图片和canvas大小
let posterImg = this.detailObj.posterImg
? this.detailObj.posterImg
: this.detailObj.activityDetailHeadImg
// 设置大图的url
this.downImg(posterImg, this.posterOptions.poster, 'url')
uni.getImageInfo({
src: posterImg,
success(res) {
let imgW = res.width //图片宽度
let imgH = res.height //图片高度
let per = imgW / that.screenWidth
that.$set(
that.posterOptions.poster,
'canvasWidth',
that.screenWidth
)
// 是否设置分享海报
let canvasHeight = isHaveImg
? imgH / per
: imgH / per + that.posterOptions.buttonOption.h
that.$set(that.posterOptions.poster, 'canvasHeight', canvasHeight)
// 设置二维码位置
let codeX = isHaveImg ? that.screenWidth - 95 : 5
that.$set(that.posterOptions.QrOption, 'x', codeX)
let codeY = isHaveImg ? imgH / per - 95 : canvasHeight - 95
that.$set(that.posterOptions.QrOption, 'y', codeY)
if (!isHaveImg) {
// 如果没有设置分享海报
that.$set(
that.posterOptions.buttonOption,
'w',
that.screenWidth
)
that.$set(that.posterOptions.buttonOption, 'y', imgH / per)
that.$set(that.posterOptions.textOption, 'x', codeX + 120)
that.$set(that.posterOptions.textOption, 'y', codeY + 40)
}
},
})
}
})
},
// 将网络图片缓存到本地
downImg(img, obj, name) {
let that = this
uni.downloadFile({
url: img,
success: (res) => {
that.$set(obj, name, res.tempFilePath)
},
})
},
// 分享方法
fx(val) {
let url = window.location.href
// 获取usercode
let urls
let userInfo = this.$utils.getStorageSync('userInfo')
let routes = getCurrentPages() // 获取当前打开过的页面路由数组
let curParam =
routes[routes.length - 1].options ||
routes[routes.length - 1].$route.query //获取路由参数
if (userInfo) {
curParam.userCode = userInfo.userCode
}
let a = this.$utils.pageliks(curParam, routes)
let b = window.location.origin
if (userInfo) {
urls = `${b}${a}`
} else {
urls = url
}
let data = {
url: url,
}
let shares = {
title: this.detailObj.shareTitle || this.$config.serviceProvider,
link: urls,
desc:this.detailObj.shareContent||'',
imgUrl: this.detailObj.shareImg || '',
}
if (val == 1) {
getShopWxConfig(data, shares, val)
} else {
share(shares)
}
},
},
}
</script>
<style lang="scss" scoped>
.width-100 {
width: 100%;
}
.contentBgc {
min-height: 100vh;
width: 100vw;
padding-bottom: 20rpx;
}
</style>
another
<template>
<div class="poster-wrapper" @click="closePoster($event)">
<div class="poster-content">
<canvas
canvas-id="qrcode"
v-if="qrcode"
:style="{ opacity: 0, position: 'absolute', top: '-1000px' }"
></canvas>
<canvas
canvas-id="poster"
v-if="!iscomplete"
:style="{
width: cansWidth + 'px',
height: cansHeight + 'px',
opacity: 0,
}"
></canvas>
<image
v-if="iscomplete"
:style="{ width: cansWidth + 'px', height: cansHeight + 'px' }"
:src="tempFilePath"
@longpress="longpress"
></image>
</div>
</div>
</template>
<script>
export default {
data() {
return {
bgImg:
'https://cdn.img.up678.com/ueditor/upload/image/20211130/1638258070231028289.png', //画布背景图片
cansWidth: 288, // 画布宽度
cansHeight: 410, // 画布高度
projectImgWidth: 223, // 中间展示图片宽度
projectImgHeight: 167, // 中间展示图片高度
schoolImgWidth: 110, // 中间展示高校宽度
schoolImgHeight: 110, // 中间展示高校高度
qrShow: true, // 二维码canvas
qrData: null, // 二维码数据
tempFilePath: '', // 生成图路径
iscomplete: false, // 是否生成图片
}
},
created() {
this.ctx = uni.createCanvasContext('poster', this)
},
methods: {
closePoster(e) {
if (e.target.id === e.currentTarget.id) {
// 关闭
this.$emit('close')
}
},
// 绘制分享图片
async drawerposter(name = '南京雨花台区', url, projectImg) {
uni.showLoading({
title: '加载中....',
mask: true,
})
await this.createQrcode(url)
//背景
await this.drawWebImg({
url: this.bgImg,
x: 0,
y: 0,
width: this.cansWidth,
height: this.cansHeight,
})
// 展示图
await this.drawWebImg({
url: projectImg,
x: 33,
y: 90,
width: this.projectImgWidth,
height: this.projectImgHeight,
})
await this.drawerText({
text: name,
x: 15,
y: 285,
color: '#241D4A',
size: 15,
bold: true,
center: true,
shadowObj: { x: '0', y: '4', z: '4', color: 'rgba(173,77,0,0.22)' },
})
// 二维码
await this.drawQrcode()
this.tempFilePath = await this.saveCans()
this.iscomplete = true
uni.hideLoading()
},
// 绘制分享学校
async drawposterschool(name, url, shoolImg) {
uni.showLoading({
title: '加载中...',
mask: true,
})
await this.createQrcode()
// 背景
await this.drawWebImg({
url: this.bgImg,
x: 0,
y: 0,
width: this.schoolImgWidth,
height: this.schoolImgHeight,
})
await this.drawText({
text: name,
x: 15,
y: 285,
color: '#241D4A',
size: 15,
bold: true,
center: true,
shadowObj: { x: '0', y: '4', z: '4', color: 'rgba(173,77,0,0.22' },
})
// 二维码
await this.drawQrcode()
this.tempFilePath = await this.saveCans()
this.iscomplete = true
uni.hideLoading()
},
drawWebImg(conf) {
return new Promise((resolve, reject) => {
uni.downloadFile({
url: conf.url,
success: (res) => {
this.ctx.drawImage(
res.tempFilePath,
conf.x,
conf.y,
conf.width ? conf.width : '',
conf.height ? conf.height : ''
)
this.ctx.draw(true, () => {
resolve()
})
},
fail: (err) => {
reject(err)
},
})
})
},
drawSchoolImg(conf) {
return new Promise((resolve,reject)=>{
uni.downloadFile({
url:conf.url,
success:(res)=>{
this.ctx.save()
this.ctx.beginPath()
this.ctx.arc(135,170,70,0,2*Math.PI)
// this.ctx.setFillStyle('blue')
// this.ctx.fill()
this.ctx.clip()
this.ctx.drawImage(res.tempFilePath,conf.x,conf.y,conf.width,conf.height)
this.ctx.restore()
this.ctx.draw(true,()=>{
resolve()
})
},
fail:err=>{
reject(err)
}
})
})
},
drawText(conf){
return new Promise((resolve,reject)=>{
this.ctx.restore()
this.stx.setFillStyle(conf.color)
if(conf.bold)this.ctx.font = `normal bold ${conf.size}px sans-serif`
this.ctx.setFontSize(conf.size)
let x = conf.x
conf.text = this.fittingString(this.ctx,conf.text,280)
if(conf.center){
let len = this.ctx.measureText(conf.text)
x = this.cansWidth/2 - len.width/2 +2
}
this.ctx.fillText(conf.text,x,conf.y)
this.ctx.draw(true,()=>{
this.ctx.save()
resolve()
})
})
},
// 文本标题溢出隐藏处理
fittingString(_ctx,str,maxWidth){
let strWidth = _ctx.measureText(str).width;
const ellipsis = "..."
const ellipsisWidth = _ctx.measureText(ellipsis).width;
if(strWidth<=maxWidth||maxWidth<=ellipsisWidth){
return str
}else{
var len = str.length
while(strWidth >= maxWidth-ellipsis&&len-->0){
str = str.slice(0,len)
strWidth = _ctx.measureText(str).width
}
return str + ellipsis
}
},
// 生成二维码
createQrcode(qrcodeUrl){
const config = {host:window.location.origin}
return new Promise((resolve,reject)=>{
let url = `${config.host}${qrcodeUrl}`
try{
new qrCode({
canvasId:'qrcode',
usingComponents:true,
context:this,
text:url,
size:130,
cbResult:(res)=>{
this.qrShow = false
this.qrData = res
resolve()
}
})
}catch(err){
reject(err)
}
})
},
// 画二维码 this.qrData为生成的二维码资源
drawQrcode(conf = {x:185,y:335,width:100,height:50}){
return new Promise((resolve,reject)=>{
this.ctx.drawImage(this.qrData,conf.x,conf.y,conf.width,conf.height)
this.ctx.draw(true,()=>{
resolve()
})
})
},
// canvs => images
saveCans(){
return new Promise((resolve,reject)=>{
uni.canvasToTempFilePath({
x:0,
y:0,
canvasId:'poster',
success:(res)=>{
resolve(res.tempFilePath)
},
fail:(err)=>{
uni.hideLoading()
reject(err)
}
},this)
})
}
},
}
</script>
<style lang="scss" scoped></style>
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。