基于uniapp框架,通过封装可复用组件实现二维码海报生成功能,利用uQRCode等插件生成二维码,结合canvas绘制海报背景、文字及二维码布局,支持自定义样式(尺寸、颜色、Logo),组件提供预览和保存接口,调用uni.saveImageToPhotosAlbum实现海报本地存储,适用于活动推广、商品分享等场景,提升开发效率与用户体验。
Uniapp自定义组件实现二维码海报生成与分享
在移动端应用开发中,生成带二维码的海报已成为一种常见需求,广泛应用于活动推广、用户裂变分享、身份核验、产品展示等多种场景,Uniapp作为一套跨端开发框架,通过一套代码即可编译到iOS、Android、H5、小程序等多个平台,为开发者提供了极大的便利,本文将详细介绍如何基于Uniapp创建自定义组件,实现二维码生成、海报布局设计、跨端适配优化以及分享保存功能,帮助开发者快速构建高质量的海报生成功能。
功能需求与技术选型
核心需求
- 二维码生成:根据动态传入的内容(如活动链接、用户ID、文本信息等)生成高质量二维码图片
- 海报布局设计:支持自定义背景图、标题文字、描述信息、二维码位置及样式,满足不同业务场景需求
- 跨端兼容适配:确保在iOS、Android、H5、小程序等多平台上都能正常显示和保存
- 保存与分享功能:支持将生成的海报保存到本地相册,并能调用系统分享接口进行传播
技术选型
- 二维码生成:选用
qrcode库,该库轻量级、性能优异,支持Canvas绘制,兼容多端环境 - 海报绘制:基于Uniapp原生
canvasAPI,确保跨端兼容性最佳,无需额外引入第三方依赖 - 图片处理:利用
uni.getImageInfo获取图片信息,同时支持本地图片和网络图片资源 - 权限处理:通过
uni.authorize请求相册写入权限,针对iOS/Android平台进行特殊处理 - 性能优化:采用异步加载和缓存机制,避免重复生成相同内容的海报
自定义组件创建
组件目录结构
在Uniapp项目中,建议按照以下结构组织自定义组件:
components/
└── qrcode-poster/ // 组件文件夹
├── qrcode-poster.vue // 组件模板
├── qrcode-poster.js // 组件逻辑
├── qrcode-poster.json // 组件配置
└── utils/ // 工具函数
├── qrcode-generator.js // 二维码生成工具
└── image-loader.js // 图片加载工具
组件配置(qrcode-poster.json)
{
"component": true,
"usingComponents": {}
}
组件核心实现
组件模板(qrcode-poster.vue)
<template>
<view class="poster-container">
<!-- 海报画布 -->
<canvas
class="poster-canvas"
canvas-id="posterCanvas"
:style="{ width: canvasWidth + 'px', height: canvasHeight + 'px' }"
:disable-scroll="true"
></canvas>
<!-- 加载状态提示 -->
<view class="loading-mask" v-if="isLoading">
<text>海报生成中...</text>
</view>
<!-- 操作按钮(可选) -->
<view class="poster-actions" v-if="showActions">
<button @click="savePoster" class="action-btn save-btn">保存海报</button>
<button @click="sharePoster" class="action-btn share-btn">分享海报</button>
</view>
</view>
</template>
<style>
.poster-container {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
}
.poster-canvas {
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
}
.loading-mask {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
color: #fff;
border-radius: 8px;
}
.poster-actions {
margin-top: 20rpx;
display: flex;
gap: 20rpx;
}
.action-btn {
padding: 12rpx 30rpx;
border-radius: 30rpx;
font-size: 28rpx;
}
.save-btn {
background: #07c160;
color: #fff;
}
.share-btn {
background: #1989fa;
color: #fff;
}
</style>
组件逻辑(qrcode-poster.js)
import QRCode from 'qrcode'
import { generateQRCode } from './utils/qrcode-generator'
import { loadImage } from './utils/image-loader'
export default {
name: 'QrcodePoster',
props: {
// 海报配置
posterConfig: {
type: Object,
default: () => ({
// 画布尺寸(根据平台适配)
canvasWidth: 375, // H5/小程序逻辑像素,iOS/Android 物理像素需转换
canvasHeight: 667,
// 背景图(支持本地路径/网络地址)
bgImage: '',
// 二维码配置
qrCodeConfig: {
content: '',
size: 200,
color: '#000000',
backgroundColor: '#ffffff',
correctLevel: 'M'
},
// 文字配置
title: {
text: '',
fontSize: 32,
color: '#333333',
bold: true,
position: { x: 0, y: 100 }
},
description: {
text: '',
fontSize: 24,
color: '#666666',
position: { x: 0, y: 150 },
maxWidth: 300,
lineHeight: 1.5
}
})
},
// 是否显示操作按钮
showActions: {
type: Boolean,
default: true
}
},
data() {
return {
isLoading: false,
canvasContext: null
}
},
mounted() {
this.initCanvas()
},
methods: {
// 初始化画布
initCanvas() {
const query = uni.createSelectorQuery().in(this)
query.select('#posterCanvas').fields({ node: true, size: true })
.exec((res) => {
const canvas = res[0].node
const ctx = canvas.getContext('2d')
// 根据平台调整画布尺寸
const dpr = uni.getSystemInfoSync().pixelRatio || 1
canvas.width = this.posterConfig.canvasWidth * dpr
canvas.height = this.posterConfig.canvasHeight * dpr
ctx.scale(dpr, dpr)
this.canvasContext = ctx
this.drawPoster()
})
},
// 绘制海报
async drawPoster() {
this.isLoading = true
const ctx = this.canvasContext
try {
// 1. 绘制背景
if (this.posterConfig.bgImage) {
const bgImage = await loadImage(this.posterConfig.bgImage)
ctx.drawImage(bgImage, 0, 0, this.posterConfig.canvasWidth, this.posterConfig.canvasHeight)
} else {
ctx.fillStyle = '#ffffff'
ctx.fillRect(0, 0, this.posterConfig.canvasWidth, this