在uni-app中使用Painter生成小程序海报
-
安装Painter
从GitHub下载Painter组件:https://github.com/Kujiale-Mobile/Painter
将painter文件夹复制到uni-app项目的components
目录下 -
配置页面
在需要使用海报的页面的pages.json
中配置
{
"path": "pages/share/index",
"style": {
"navigationBarTitleText": "分享海报",
"usingComponents": {
"painter": "/components/painter/painter"
}
}
}
- 在页面中使用Painter
<template>
<!-- 海报详情 -->
<view class="wrap">
<!-- 引入 Painter 组件,隐藏绘制层 -->
<painter :palette="posterData" @imgOK="onImgOK" @imgErr="onImgErr"
style="position: absolute; left: -9999rpx;" />
<!-- 显示生成的海报 -->
<image v-if="imagePath" :src="imagePath" show-menu-by-longpress
mode="aspectFill" style="width: 90%; height: 1200rpx;margin-left: 5%;" />
</view>
</template>
<script>
import {
getPosterConfigDetail,
saveAgentPoster
} from '@/api/home.js'
export default {
data() {
return {
userName: "", // 动态用户名称
phoneNumber: "", // 动态电话号码
qrcodeBase64: "", // 从接口获取的 Base64 小程序码
posterData: {}, // Painter 配置数据
imagePath: "", // 生成的海报路径
posterImageUrl: "", //海报背景图
oldUserName: "",//上次绘制海报时的用户名
oldPhoneNumber: "",
posterName: '',
mainImageId: '',
posterCode: '',
templateId: ''
}
},
onLoad(option) {
// id为海报模板
this.id = option.id
this.init()
},
methods: {
init() {
// 后台使用初始信息生成微信小程序码
const posterConfigDetail = await getPosterConfigDetail({
id: this.id
})
this.userName = res.data.userName
this.phoneNumber = res.data.mobile
this.posterCode = res.data.posterCode
this.templateId = res.data.id
this.posterImageUrl = res.data.posterImageUrl
this.posterName = res.data.posterName
this.qrcodeBase64 = res.data.wxCodeContent
this.oldUserName = this.userName
this.oldPhoneNumber = this.phoneNumber
this.generatePoster()
},
// 生成海报配置
async generatePoster() {
const posterConfig = {
width: "750rpx",
height: "1334rpx",
background: "#ffffff",
views: [
// 背景图
{
type: 'image',
url: `${this.posterImageUrl}`,
css: {
width: '750rpx',
height: '1334rpx'
}
},
{
type: "text",
text: `联系人:${this.userName}`, // 直接注入数据
css: {
fontSize: "32rpx",
bottom: "250rpx",
left: "250rpx"
}
},
{
type: "text",
text: `手机号:${this.phoneNumber}`,
css: {
fontSize: "28rpx",
bottom: "200rpx",
left: "250rpx"
}
},
{
type: "image",
url: `data:image/png;base64,${this.qrcodeBase64}`,
css: {
width: "200rpx",
height: "200rpx",
bottom: "350rpx",
left: "275rpx"
}
}
]
};
this.posterData = posterConfig;
console.log(this.posterData, ' this.posterData')
// 手动触发绘制
this.$forceUpdate()
},
// 生成海报成功
onImgOK(e) {
this.imagePath = e.detail.path;
}
</script>
- 预览、保存图片到相册
<template>
<!-- 海报详情 -->
<view class="wrap">
<!-- 引入 Painter 组件,隐藏绘制层 -->
<painter :palette="posterData" @imgOK="onImgOK" @imgErr="onImgErr"
style="position: absolute; left: -9999rpx;" />
<!-- 显示生成的海报 -->
<h1 style="text-align: center;font-size: 40rpx;margin: 20rpx 0;color:#3ccc97;">{{posterName}}</h1>
<image v-if="imagePath" :src="imagePath" show-menu-by-longpress
mode="aspectFill" style="width: 90%; height: 1200rpx;margin-left: 5%;" />
<view class="uni-form">
<view class="uni-form-item uni-column">
<view class="title">联系人: </view>
<input class="uni-input" placeholder="请输入联系人" v-model="userName"></input>
</view>
<view class="uni-form-item uni-column">
<view class="title">手机号码: </view>
<input class="uni-input" type="number" maxlength="11" v-model="phoneNumber"
placeholder="请输入手机号码"></input>
</view>
<view class="uni-form-item uni-column">
<view class="title">海报标题: </view>
<input class="uni-input" v-model="posterName" placeholder="请输入海报标题"></input>
</view>
</view>
<!-- 触发生成的按钮 -->
<view class="" style="padding-bottom: 60rpx;overflow: hidden;width: 100%">
<button class="btnStyle" @click="previewImg">预览</button>
<button class="btnStyle" @click="generateShare">生成并分享</button>
<button class="btnStyle" @click="saveToAlbum">保存到相册</button>
</view>
</view>
</template>
<script>
import {
getPosterConfigDetail,
saveAgentPoster
} from '@/api/home.js'
export default {
data() {
return {
userName: "", // 动态用户名称
phoneNumber: "", // 动态电话号码
qrcodeBase64: "", // 从接口获取的 Base64 小程序码
posterData: {}, // Painter 配置数据
imagePath: "", // 生成的海报路径
posterImageUrl: "", //海报背景图
oldUserName: "",//上次绘制海报时的用户名
oldPhoneNumber: "",
posterName: '',
mainImageId: '',
posterCode: '',
templateId: ''
}
},
onLoad(option) {
this.token = uni.getStorageSync('token')
// id为海报模板
this.id = option.id
this.init()
},
created() {
},
methods: {
init() {
// 后台使用初始信息生成微信小程序码
const posterConfigDetail = await getPosterConfigDetail({
id: this.id
})
this.userName = res.data.userName
this.phoneNumber = res.data.mobile
this.posterCode = res.data.posterCode
this.templateId = res.data.id
this.posterImageUrl = res.data.posterImageUrl
this.posterName = res.data.posterName
this.qrcodeBase64 = res.data.wxCodeContent
this.oldUserName = this.userName
this.oldPhoneNumber = this.phoneNumber
this.generatePoster()
},
asnyc previewImg() {
const needStatus = await this.needRegenerate()
if (needStatus) {
await this.generatePoster()
// 延迟0.5秒,等待页面重绘
await this.delay(500)
}
wx.previewImage({
current: this.imagePath,
urls: [this.imagePath]
})
},
// 修改用户信息后,重新生成小程序码及海报编码(生成并分享和保存到相册会上传数据到后台,可能会存储多条数据,以海报编码做区分)
async needRegenerate() {
// 修改用户信息后,重新生成小程序码
if (this.userName != this.oldUserName || this.phoneNumber != this.oldPhoneNumber) {
let that = this
await new Promise((resolve, reject) => {
getPosterConfigDetail({
id: this.id
}).then(res => {
if (res.code == 0) {
that.posterCode = res.data.posterCode
that.qrcodeBase64 = res.data.wxCodeContent
that.oldUserName = that.userName
that.oldPhoneNumber = that.phoneNumber
}
resolve()
}).catch(e => {
reject(e)
})
});
return true
}
return false
}
// 生成海报配置
async generatePoster() {
const posterConfig = {
width: "750rpx",
height: "1334rpx",
background: "#ffffff",
views: [
// 背景图
{
type: 'image',
url: `${this.posterImageUrl}`,
css: {
width: '750rpx',
height: '1334rpx'
}
},
{
type: "text",
text: `联系人:${this.userName}`, // 直接注入数据
css: {
fontSize: "32rpx",
bottom: "250rpx",
left: "250rpx"
}
},
{
type: "text",
text: `手机号:${this.phoneNumber}`,
css: {
fontSize: "28rpx",
bottom: "200rpx",
left: "250rpx"
}
},
{
type: "image",
url: `data:image/png;base64,${this.qrcodeBase64}`,
css: {
width: "200rpx",
height: "200rpx",
bottom: "350rpx",
left: "275rpx"
}
}
]
};
this.posterData = posterConfig;
console.log(this.posterData, ' this.posterData')
// 手动触发绘制
this.$forceUpdate()
},
// 生成海报成功
onImgOK(e) {
this.imagePath = e.detail.path;
},
async delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
},
// 生成并分享
async generateShare() {
const needStatus = await this.needRegenerate()
if (needStatus) {
await this.generatePoster()
// 延迟0.5秒,等待页面重绘
await this.delay(500)
}
// 保存海报数据
this.save()
const that = this
wx.showShareImageMenu({
path: that.imagePath, //图片地址必须为本地路径或者临时路径
success: (re) => {
console.log(re, "分享成功")
},
fail: (re) => {
console.log(re, "分享失败")
}
});
},
// 保存到相册
saveToAlbum() {
const needStatus = await this.needReGenerate()
if (needStatus) {
await this.generatePoster()
// 延迟0.5秒,等待页面重绘
await this.delay(500)
}
// 保存海报数据
this.save()
const that = this
uni.saveImageToPhotosAlbum({
filePath: that.imagePath,
success: () => {
uni.showToast({
title: '保存成功',
icon: 'success'
});
},
fail: (err) => {
console.error('保存失败:', err);
if (err.errMsg.includes('auth')) {
that.showAuthSetting('需要相册权限才能保存图片');
} else {
uni.showToast({
title: '保存失败',
icon: 'none'
});
}
}
});
},
// 显示权限设置引导
showAuthSetting(content) {
uni.showModal({
title: '权限申请',
content: content || '需要您授权权限才能继续操作',
confirmText: '去设置',
success: (res) => {
if (res.confirm) {
uni.openSetting();
}
}
});
},
// 保存到后台
sync save() {
saveAgentPoster({
mainImageId: this.mainImageId,
posterCode: this.posterCode,
posterName: this.posterName,
templateId: this.templateId,
}).then(res => {
if (res.code == 0) {
console.log("请求成功")
}
})
},
onImgErr(e) {
console.log(e, '生成海报出错了')
}
}
}
</script>
<style scoped lang="scss">
.wrap {
width: 100vw;
height: 100%;
background-color: #f7f7f7;
// position: relative;
}
.uni-form {
background-color: #f7f7f7;
width: 96%;
margin-left: 2%;
.uni-form-item {
margin-top: 20rpx;
background-color: #fff;
.uni-input {
color: #333;
font-size: 30rpx;
height: 81rpx;
padding-left: 10rpx;
// border: none;
border-radius: 10rpx;
}
.title {
font-size: 30rpx;
color: #333;
// font-weight: 600;
line-height: 81rpx;
margin-bottom: 20rpx;
float: left;
width: 150rpx;
text-align: right;
padding-left: 5rpx;
}
.u-button--square {
color: #3ccc97 !important;
}
.u-input__content__field-wrapper__field {
text-align: left !important;
}
}
}
.btnStyle {
width: 28%;
float: left;
border-radius: 20rpx;
height: 80rpx;
line-height: 80rpx;
margin-left: 4%;
}
</style>