UniApp项目实战:我把uQRCode二维码生成做成了可复用的Vue组件(支持动态配置标题/Logo/样式)
2026/6/14 2:18:34 网站建设 项目流程

UniApp高级实战:打造企业级可配置二维码组件全攻略

在移动互联网时代,二维码已成为连接线上线下场景的重要媒介。对于UniApp开发者而言,如何在项目中高效、灵活地生成各种风格的二维码,同时保证代码的可维护性和复用性,是一个值得深入探讨的技术课题。本文将带你从零开始,构建一个支持动态配置标题、Logo和样式的企业级二维码组件,解决实际开发中的痛点问题。

1. 工程化思维下的组件设计

在开始编码之前,我们需要先明确组件的设计目标和架构思路。一个优秀的可复用组件应该具备以下特点:

  • 高内聚低耦合:组件内部逻辑自包含,对外提供清晰的接口
  • 灵活可配置:通过Props支持各种定制化需求
  • 性能优化:合理处理异步操作和绘制性能
  • 易用性:提供简洁的API和良好的开发者体验

1.1 组件Props设计

基于这些原则,我们首先设计组件的Props接口:

props: { // 二维码内容 content: { type: String, required: true }, // 二维码尺寸 size: { type: Number, default: 300 }, // 二维码标题 title: { type: String, default: '' }, // 标题位置:top/center/bottom titlePosition: { type: String, default: 'bottom', validator: (value) => ['top', 'center', 'bottom'].includes(value) }, // Logo图片URL logo: { type: String, default: '' }, // 边框宽度 borderWidth: { type: Number, default: 0 }, // 二维码前景色 foregroundColor: { type: String, default: '#000000' }, // 二维码背景色 backgroundColor: { type: String, default: '#ffffff' } }

1.2 组件核心架构

组件的主体结构如下:

<template> <view class="qrcode-container"> <canvas :id="canvasId" :canvas-id="canvasId" :style="canvasStyle" /> <slot name="extra"></slot> </view> </template> <script> import UQRCode from '@uqrcode/js' export default { name: 'QrCodeGenerator', props: { /* 上面定义的props */ }, data() { return { canvasId: `qrcode-${Date.now()}`, isLoading: false } }, computed: { canvasStyle() { return { width: `${this.size}px`, height: `${this.size}px` } } }, methods: { /* 核心方法 */ } } </script>

2. 核心绘制逻辑实现

2.1 二维码基础生成

首先实现最基本的二维码生成功能:

methods: { async generateQRCode() { if (this.isLoading) return this.isLoading = true try { const qr = new UQRCode() qr.data = this.content qr.size = this.size qr.foregroundColor = this.foregroundColor qr.backgroundColor = this.backgroundColor // 预留边框空间 if (this.borderWidth > 0) { qr.margin = this.borderWidth + 10 } qr.make() const ctx = uni.createCanvasContext(this.canvasId, this) qr.canvasContext = ctx await this.drawBackground(ctx, qr) await qr.drawCanvas(false) this.$emit('generated', { canvasId: this.canvasId }) } catch (error) { console.error('生成二维码失败:', error) this.$emit('error', error) } finally { this.isLoading = false } } }

2.2 背景与边框绘制

为了支持自定义背景和边框,我们需要单独处理这些绘制逻辑:

async drawBackground(ctx, qr) { // 清空画布 ctx.setFillStyle(this.backgroundColor) ctx.fillRect(0, 0, this.size, this.size) // 绘制边框 if (this.borderWidth > 0) { ctx.setFillStyle(this.foregroundColor) const offset = this.title && this.titlePosition === 'top' ? 40 : 0 // 四边边框 ctx.fillRect(0, offset, this.borderWidth, this.size) // 左 ctx.fillRect(this.size - this.borderWidth, offset, this.borderWidth, this.size) // 右 ctx.fillRect(0, offset, this.size, this.borderWidth) // 上 ctx.fillRect(0, this.size - this.borderWidth + offset, this.size, this.borderWidth) // 下 } // 绘制标题(非居中情况) if (this.title && ['top', 'bottom'].includes(this.titlePosition)) { await this.drawTextTitle(ctx) } }

2.3 标题与Logo处理

标题和Logo的绘制是最复杂的部分,需要考虑多种排列组合情况:

async drawTextTitle(ctx) { ctx.setFontSize(16) ctx.setFillStyle(this.foregroundColor) ctx.setTextAlign('center') const textWidth = ctx.measureText(this.title).width const maxWidth = this.size - 20 let lines = [] // 文本换行处理 if (textWidth > maxWidth) { let line = '' for (const char of this.title) { if (ctx.measureText(line + char).width <= maxWidth) { line += char } else { lines.push(line) line = char } } if (line) lines.push(line) } else { lines = [this.title] } // 计算绘制位置 const lineHeight = 20 const totalHeight = lines.length * lineHeight let yPos = 0 if (this.titlePosition === 'top') { yPos = 10 // 调整二维码位置 qr.getDrawModules().forEach(item => { item.y += totalHeight + 10 }) } else { yPos = this.size - totalHeight - 10 } // 绘制每行文本 lines.forEach((line, index) => { ctx.fillText(line, this.size / 2, yPos + index * lineHeight) }) } async drawCenterLogo(ctx) { if (!this.logo && !this.title) return // 绘制Logo背景 const logoSize = this.size * 0.2 const logoX = (this.size - logoSize) / 2 const logoY = (this.size - logoSize) / 2 ctx.setFillStyle('#ffffff') ctx.fillRect(logoX, logoY, logoSize, logoSize) if (this.logo) { // 处理网络图片 const tempFilePath = await this.downloadImage(this.logo) ctx.drawImage(tempFilePath, logoX, logoY, logoSize, logoSize) } else if (this.title) { // 绘制居中标题 ctx.setFontSize(14) ctx.setFillStyle('#000000') ctx.setTextAlign('center') ctx.setTextBaseline('middle') ctx.fillText(this.title, this.size / 2, this.size / 2) } }

3. 性能优化与高级功能

3.1 图片下载与缓存

网络Logo图片的处理需要考虑下载和缓存:

async downloadImage(url) { try { const cacheKey = `image_cache_${md5(url)}` const cachePath = uni.getStorageSync(cacheKey) if (cachePath) { return cachePath } const { tempFilePath } = await uni.downloadFile({ url }) uni.setStorageSync(cacheKey, tempFilePath) return tempFilePath } catch (error) { console.error('图片下载失败:', error) throw error } }

3.2 绘制完成回调

为了更好的开发者体验,我们提供绘制完成的回调:

watch: { content: { immediate: true, handler() { this.$nextTick(() => { this.generateQRCode() }) } } } // 在drawCanvas完成后 await qr.drawCanvas(false) this.$emit('generated', { canvasId: this.canvasId, size: this.size, content: this.content })

3.3 导出图片功能

添加导出图片的便捷方法:

methods: { async exportToTempFilePath() { return new Promise((resolve, reject) => { uni.canvasToTempFilePath({ canvasId: this.canvasId, success: (res) => resolve(res.tempFilePath), fail: reject }, this) }) } }

4. 组件集成与使用示例

4.1 在页面中使用组件

<template> <view> <qrcode-generator content="https://example.com" size="300" title="扫描二维码访问" title-position="bottom" logo="https://example.com/logo.png" border-width="5" @generated="handleGenerated" /> <button @click="saveQRCode">保存二维码</button> </view> </template> <script> import QrcodeGenerator from '@/components/QrcodeGenerator.vue' export default { components: { QrcodeGenerator }, methods: { handleGenerated({ canvasId }) { console.log('二维码生成完成', canvasId) }, async saveQRCode() { const tempFilePath = await this.$refs.qrcode.exportToTempFilePath() uni.saveImageToPhotosAlbum({ filePath: tempFilePath, success: () => uni.showToast({ title: '保存成功' }) }) } } } </script>

4.2 动态配置示例

// 动态改变二维码配置 updateQRCode() { this.qrConfig = { content: `https://example.com/user/${this.userId}`, title: `用户专属二维码: ${this.userName}`, logo: this.userAvatar, borderWidth: this.isVip ? 8 : 0, foregroundColor: this.isVip ? '#FFD700' : '#000000' } }

4.3 多场景适配

通过slot支持更灵活的布局:

<qrcode-generator :content="qrContent" :size="300" > <template #extra> <view class="qr-tip">长按识别二维码</view> </template> </qrcode-generator>

5. 常见问题与解决方案

5.1 Canvas层级问题

在UniApp中,canvas组件有较高的层级,可能会覆盖其他元素。解决方案:

  • 使用cover-view覆盖需要显示的内容
  • 通过条件渲染控制显示顺序
  • 将二维码生成后转换为图片显示

5.2 图片跨域问题

处理网络Logo图片时可能遇到的跨域问题:

// 在manifest.json中配置 "networkTimeout": { "downloadFile": 60000 }, "mp-weixin": { "permission": { "scope.writePhotosAlbum": { "desc": "用于保存二维码到相册" } } }

5.3 性能优化建议

对于频繁更新的二维码:

  • 使用防抖控制生成频率
  • 考虑使用worker线程生成二维码
  • 对相同内容的二维码进行缓存
// 防抖示例 import { debounce } from 'lodash-es' export default { methods: { generateQRCode: debounce(function() { // 实际生成逻辑 }, 300) } }

6. 扩展与进阶

6.1 支持更多样式配置

可以扩展支持更多样式选项:

props: { // 点形状:square/circle/round/diamond dotShape: { type: String, default: 'square' }, // 点缩放比例 dotScale: { type: Number, default: 1, validator: (value) => value > 0 && value <= 2 } } // 在生成时应用 qr.dotShape = this.dotShape qr.dotScale = this.dotScale

6.2 服务端渲染支持

对于需要服务端生成的情况:

// 在Node.js环境中使用 const UQRCode = require('@uqrcode/js') const { createCanvas } = require('canvas') function generateQRCodeOnServer(options) { const canvas = createCanvas(options.size, options.size) const qr = new UQRCode() qr.data = options.content qr.size = options.size qr.canvasContext = canvas.getContext('2d') qr.make() return qr.drawCanvas() }

6.3 二维码解析功能

添加解析二维码的能力:

methods: { async scanQRCode() { try { const res = await uni.chooseImage({ count: 1 }) const tempFilePath = res.tempFilePaths[0] const result = await this.parseQRCode(tempFilePath) this.$emit('parsed', result) } catch (error) { this.$emit('error', error) } }, parseQRCode(filePath) { return new Promise((resolve, reject) => { // 使用第三方库解析二维码 // ... }) } }

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询