| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219 |
- <template>
- <view class="qr-container">
- <canvas :id="canvasId" :canvas-id="canvasId" class="qr-canvas" :width="width" :height="height" :style="{
- width: width + 'px',
- height: height + 'px'
- }" />
- </view>
- </template>
- <script setup>
- import { onMounted, watch } from 'vue'
- import QRCode from 'qrcode'
- // TextEncoder polyfill for uni-app
- if (typeof TextEncoder === 'undefined') {
- globalThis.TextEncoder = class TextEncoder {
- encode(str) {
- const buf = new ArrayBuffer(str.length)
- const bufView = new Uint8Array(buf)
- for (let i = 0, strLen = str.length; i < strLen; i++) {
- bufView[i] = str.charCodeAt(i)
- }
- return bufView
- }
- }
- }
- // TextDecoder polyfill for uni-app
- if (typeof TextDecoder === 'undefined') {
- globalThis.TextDecoder = class TextDecoder {
- decode(uint8arr) {
- let encodedString = String.fromCharCode.apply(null, uint8arr)
- let decodedString = decodeURIComponent(escape(encodedString))
- return decodedString
- }
- }
- }
- const props = defineProps({
- /** 二维码内容 */
- text: {
- type: String,
- required: true
- },
- /** 宽高 */
- width: {
- type: Number,
- default: 200
- },
- height: {
- type: Number,
- default: 200
- },
- /** 颜色 */
- colorDark: {
- type: String,
- default: '#000000'
- },
- colorLight: {
- type: String,
- default: '#ffffff'
- },
- /** 中间 logo(可选) */
- logo: {
- type: String,
- default: ''
- },
- /** logo 占比 */
- logoScale: {
- type: Number,
- default: 0.22
- }
- })
- const canvasId = `qr_${Date.now()}`
- /** 绘制二维码 */
- async function drawQr() {
- console.log('开始绘制二维码:', props.text)
- if (!props.text) {
- console.warn('二维码文本为空')
- return
- }
- try {
- // 延迟确保canvas已初始化
- await new Promise(resolve => setTimeout(resolve, 100))
- const res = await QRCode.create(props.text, {
- errorCorrectionLevel: 'H'
- })
- // 获取canvas上下文
- const ctx = uni.createCanvasContext(canvasId)
- if (!ctx) {
- console.error('无法获取canvas上下文')
- return
- }
- const size = props.width
- const count = res.modules.size
- const cellSize = size / count
- // 背景
- ctx.setFillStyle(props.colorLight)
- ctx.fillRect(0, 0, size, size)
- // 绘制二维码
- for (let row = 0; row < count; row++) {
- for (let col = 0; col < count; col++) {
- ctx.setFillStyle(
- res.modules.get(row, col)
- ? props.colorDark
- : props.colorLight
- )
- ctx.fillRect(
- col * cellSize,
- row * cellSize,
- Math.ceil(cellSize),
- Math.ceil(cellSize)
- )
- }
- }
- // 绘制到canvas
- ctx.draw(false, () => {
- console.log('二维码绘制成功')
- // 绘制 logo
- if (props.logo) {
- drawLogo(ctx)
- }
- })
- } catch (e) {
- console.error('二维码绘制错误:')
- console.error('错误类型:', e.constructor.name)
- console.error('错误信息:', e.message)
- console.error('错误堆栈:', e.stack)
- uni.showToast({
- title: '二维码生成失败',
- icon: 'none',
- duration: 2000
- })
- }
- }
- /** 绘制中间 logo */
- function drawLogo(ctx) {
- const size = props.width
- const logoSize = size * props.logoScale
- const dx = (size - logoSize) / 2
- const dy = (size - logoSize) / 2
- // 绘制白色背景
- ctx.setFillStyle('#ffffff')
- ctx.fillRect(dx - 5, dy - 5, logoSize + 10, logoSize + 10)
- // 在真机上,确保图片已加载再绘制
- const img = new Image()
- img.src = props.logo
- img.onload = () => {
- ctx.drawImage(props.logo, dx, dy, logoSize, logoSize)
- ctx.draw(true)
- }
- img.onerror = () => {
- console.error('Logo图片加载失败:', props.logo)
- }
- }
- /** 下载二维码 */
- function download() {
- uni.canvasToTempFilePath({
- canvasId,
- success(res) {
- console.log('canvas转图片成功:', res.tempFilePath)
- uni.saveImageToPhotosAlbum({
- filePath: res.tempFilePath,
- success() {
- uni.showToast({ title: '已保存', icon: 'success' })
- },
- fail(err) {
- console.error('保存图片失败:', err)
- uni.showToast({ title: '保存失败', icon: 'error' })
- }
- })
- },
- fail(err) {
- console.error('canvas转图片失败:', err)
- uni.showToast({ title: '生成图片失败', icon: 'error' })
- }
- })
- }
- onMounted(() => {
- console.log('QrCode组件已挂载,准备绘制二维码')
- drawQr()
- })
- watch(() => props.text, () => {
- console.log('二维码文本变化,重新绘制:', props.text)
- drawQr()
- })
- defineExpose({
- download
- })
- </script>
- <style scoped lang="scss">
- @import "@/uni.scss";
- .qr-container {
- display: flex;
- justify-content: center;
- align-items: center;
- }
- .qr-canvas {
- background: transparent;
- }
- </style>
|