1、背景
uniapp(vue3)开发小程序,点击海报图,将海报图与小程序码合成一张新图,并长按保存本地。
2、代码实现
- 布局
<template>
<view class="poster">
<view class="poster-tip">
点击任意海报会生成是专属小程序码,长按可保存至本地
</view>
<!-- 绘制组合海报与小程序的图 -->
<view class="poster-swiper">
<up-swiper v-if="posterList && posterList.length" @click="onSwiper" height="426" :list="posterList"
keyName="imageUrl" indicator indicatorMode="dot" indicatorActiveColor="#4396fe"
indicatorInactiveColor="#D6D6D6" previousMargin="30" nextMargin="30" circular :autoplay="false" radius="5"
bgColor="#ffffff"></up-swiper>
<view v-else-if="loading" class="poster-null">
<view class="poster-null-img">
<image class="poster-null-icon" src="../../static/ic_poster_null.png" mode="widthFix"></image>
</view>
<view class="poster-null-label">
暂未上传图片,如有需要请联系平台方
</view>
</view>
</view>
<canvas canvas-id="myCanvas" style="width: 320px; height: 426px; opacity: 0;margin-top:9999px"></canvas>
<!-- 海报预览 -->
<up-popup :show="showPoster" mode="center" @close="closePoster" @open="open" @touchMouve.prevent.stop>
<!-- 已生成海报地址 -->
<view class="poster-url" v-if="createPostUrl">
<image :src="createPostUrl" class="poster-url-img" @longpress="onSave"></image>
<!-- <canvas canvas-id="myCanvas" style="width: 320px; height: 426px;"></canvas> -->
</view>
<!--<view class="poster-url" v-else-if="postUrl">
<image :src="postUrl" class="poster-url-img"></image>
<image :src="codeUrl" class="poster-url-code">
</image>
</view>-->
</up-popup>
</template>
- 初始化
<script setup>
import {
onLoad
} from "@dcloudio/uni-app";
import {
ref,
toRaw
} from 'vue';
const userId = ref(10086)
const posterList = ref([]) // 海报列表
const copyPosterList = ref([]) // 原始海报列表
const postUrl = ref("") // 点击的海报地址
const createPostUrl = ref("") // 生成的海报地址
const showPoster = ref(false) // 展示海报
const provinceId = ref("000000"); // 选中省份ID
const codeUrl = ref("/f574daa7f65d4b859330db671d0e3b8b.png")
const closePoster = () => {
showPoster.value = false
}
const getData = () => {
posterList.value = [];
// // todo调试代码
posterList.value = [
'https://cdn.uviewui.com/uview/swiper/swiper1.png',
'https://cdn.uviewui.com/uview/swiper/swiper2.png',
'https://cdn.uviewui.com/uview/swiper/swiper3.png',
];
copyPosterList.value = [
'https://cdn.uviewui.com/uview/swiper/swiper1.png',
'https://cdn.uviewui.com/uview/swiper/swiper2.png',
'https://cdn.uviewui.com/uview/swiper/swiper3.png',
];
}
onLoad(() => {
console.log("初始化1")
getData()
})
</script>
- 点击图片
<script setup>
//点击图片
const onSwiper = (index) => {
console.log("点击图片2", index);
postUrl.value = posterList.value[index]
// 存在已生成的海报
if (posterList.value.length !== copyPosterList.value.length) {
let cachePostUrlMap = uni.getStorageSync("postUrlMap")
let key = `${provinceId.value}-${postUrl.value}`;
//点击的是海报 存在已缓存生成的海报
if (cachePostUrlMap && cachePostUrlMap[key]) {
// 获取当前海报是否存在已生成的海报地址
createPostUrl.value = cachePostUrlMap[key];
// 生成海报放在第一位
posterList.value.splice(0, 1, createPostUrl.value);
// 更新首位图
cachePostUrlMap[provinceId.value] = createPostUrl.value; // 每个省份仅缓存一张生成的最新生成的海报图片
uni.setStorageSync("postUrlMap", cachePostUrlMap);
showPoster.value = true;
} else if (index == 0) {
// 点击的是已生成的带有二维码的海报 默认将生成的代码海报插入swiper的首位
createPostUrl.value = postUrl.value;
showPoster.value = true;
} else {
console.log("生成海报3postUrl", postUrl.value)
combineImages()
}
} else {
console.log("生成海报3postUrl", postUrl.value)
combineImages();
}
}
</script>
- 生成带有小程序码的海报图
// 合成海报
const combineImages = async () => {
uni.showLoading({
title: '海报生成中'
})
const ctx = uni.createCanvasContext('myCanvas', this);
// 加载需要结合的图片
const imageInfo = await uni.getImageInfo({
src: postUrl.value
});
// 绘制需要结合的图片到canvas上
ctx.drawImage(imageInfo.path, 0, 0, 320, 426);
console.log("combineImages=>imageInfo", imageInfo)
// 加载小程序码图片
const qrCodeInfo = await uni.getImageInfo({
src: codeUrl.value
});
console.log("combineImages=>qrCodeInfo", codeUrl.value, qrCodeInfo)
// 绘制小程序码到canvas上,位置为右下角,大小为100px * 100px
const qrCodeWidth = 100;
const qrCodeHeight = 100;
const x = (320 - qrCodeWidth) / 2; // 计算小程序码的x坐标
const y = 426 - qrCodeHeight - 10; // 计算小程序码的y坐标
ctx.drawImage(qrCodeInfo.path, x, y, qrCodeWidth, qrCodeHeight);
// 将canvas内容导出为图片
ctx.draw(false, () => {
uni.canvasToTempFilePath({
canvasId: 'myCanvas',
success: (res) => {
console.log('合并后的图片路径4:', res.tempFilePath);
uploadFile(res.tempFilePath);
},
fail: (err) => {
console.error('绘制失败:', err);
}
});
});
};
- 上传生成的图片获取服务器地址
// 使用微信方法上传
const uploadFile = (res) => {
uni.showLoading({
title: '上传中',
mask: true,
});
// 需要根据环境切换下协议
let url = "http://xxx";
uni.uploadFile({
url,
header: {
"content-type": "multipart/form-data",
},
filePath: res,
name: 'file',
formData: {
processData: false,
contentType: false,
cache: false
},
success: uploadFileRes => {
uni.hideLoading();
console.log(uploadFileRes, 'uploadFileRes5');
let resData = JSON.parse(uploadFileRes.data);
console.log('resData', resData);
if (resData.success && resData.datas && resData.datas.longUrl) {
createPostUrl.value = resData.datas.longUrl;
showPoster.value = true;
console.log("createPostUrl", createPostUrl.value);
// 1.缓存当前图片 规则 key 为 用户Id-省份Id-原图地址 val 合成图地址
// 定义 postUrlMap
let postUrlMapKey = "postUrlMap"
// 更新postUrlMapVal
let cachePostUrlMap = uni.getStorageSync(postUrlMapKey)
let postUrlMapVal = cachePostUrlMap || {
"id": userId.value
}
let key = `${provinceId.value}-${postUrl.value}` // 每个省份仅缓存一张生成的图片
postUrlMapVal[key] = createPostUrl.value;
postUrlMapVal[provinceId.value] = createPostUrl.value; // 每个省份仅缓存一张生成的最新生成的海报图片
uni.setStorageSync(postUrlMapKey, postUrlMapVal);
console.log("已生成海报上传 缓存5", postUrlMapVal, createPostUrl.value, toRaw(copyPosterList.value))
// 1.判断是否存在已生成的海报 如存在则替换 不存在则插入
if (posterList.value.length == copyPosterList.value.length) {
posterList.value.unshift(createPostUrl.value);
} else {
posterList.value.splice(0, 1, createPostUrl.value);
}
console.log("更新轮播组件7", toRaw(posterList.value))
} else {
uni.showToast({
icon: 'none',
title: '上传失败,请重试',
});
}
},
fail: e => {
uni.showToast({
icon: 'none',
title: '上传失败,请重试',
});
},
});
}
- 长按保存到生成的海报到本地
// 海报保存到本地
const onSave = () => {
uni.showLoading({
title: "保存中..."
})
uni.downloadFile({
url: createPostUrl.value,
success: (res) => {
if (res.statusCode === 200) {
console.log('下载成功');
console.log("res", res.tempFilePath)
uni.saveImageToPhotosAlbum({
filePath: res.tempFilePath,
success: function(data) {
uni.hideLoading()
uni.showToast({
title: '保存成功',
icon: 'success',
duration: 2000
})
},
fail: function(err) {
wx.hideLoading()
if (err.errMsg === "saveImageToPhotosAlbum:fail auth deny" || err.errMsg ===
"saveImageToPhotosAlbum:fail:auth denied") {
console.log("当初用户拒绝,再次发起授权")
uni.showModal({
title: '提示',
content: '需要您授权保存相册',
showCancel: false,
success: () => {
uni.openSetting({
success(settingdata) {
console.log("settingdata", settingdata)
if (settingdata.authSetting['scope.writePhotosAlbum']) {
uni.showModal({
title: '提示',
content: '获取权限成功,再次点击保持到相册即可保存',
showCancel: false,
})
} else {
wx.showModal({
title: '提示',
content: '获取权限失败,将无法保存到相册哦~',
showCancel: false,
})
}
},
fail(failData) {
console.log("failData", failData)
},
complete(finishData) {
console.log("finishData", finishData)
}
})
}
})
} else {
console.log("err", err)
uni.showToast({
icon: "none",
title: "保存失败,请重试!",
duration: 2000
})
}
},
complete(res) {
console.log(res);
// wx.hideLoading()
}
});
} else {
uni.showToast({
icon: "none",
title: "保存失败,请重试!",
duration: 2000
})
}
},
fail(err) {
console.error('下载失败', err);
uni.showToast({
title: '下载失败',
icon: 'none',
duration: 2000
});
}
});
};
3.注意事项:如何在看不到canvas的情况下,进行正常绘制图片?
关于canvas隐藏,一开始使用了opacity:0,因为脱离层级,层级高,在真机上展示异常处理 解决方案: 给在外层加个css .poster { over-flow:hidden } 给canvas加个margin-top:9999px 进行隐藏
4.Q&A
如何提高合成图片的清晰度?