253 lines
6.2 KiB
Vue
253 lines
6.2 KiB
Vue
![]() |
<template>
|
|||
|
<view class="queue-code">
|
|||
|
<!-- 预览图片 -->
|
|||
|
<image
|
|||
|
v-if="previewUrl"
|
|||
|
:src="previewUrl"
|
|||
|
class="preview-image"
|
|||
|
:style="{width: width + 'px', height: height + 'px'}"
|
|||
|
mode="aspectFit"
|
|||
|
/>
|
|||
|
<!-- 用于生成图片的canvas -->
|
|||
|
<canvas
|
|||
|
canvas-id="queueCode"
|
|||
|
id="queueCode"
|
|||
|
:style="{width: width + 'px', height: height + 'px'}"
|
|||
|
/>
|
|||
|
</view>
|
|||
|
</template>
|
|||
|
|
|||
|
<script>
|
|||
|
import { getWxQRCode } from '@/api/common'
|
|||
|
import { parseLocalUrl } from '@/utils'
|
|||
|
/**
|
|||
|
* QueueCode 队伍小程序码组件
|
|||
|
* @description 用于展示和保存队伍小程序码,支持自定义底部文字
|
|||
|
* @property {String} scene - 小程序码的场景值
|
|||
|
* @property {String} title - 底部显示的队伍名称
|
|||
|
* @property {Number} width - 画布宽度,默认300
|
|||
|
* @property {Number} height - 画布高度,默认400
|
|||
|
* @property {String} page - 小程序码跳转的页面路径
|
|||
|
* @event {Function} onSave - 保存成功的回调
|
|||
|
* @event {Function} onError - 出错的回调
|
|||
|
*/
|
|||
|
export default {
|
|||
|
name: 'QueueCode',
|
|||
|
props: {
|
|||
|
scene: {
|
|||
|
type: String,
|
|||
|
required: true
|
|||
|
},
|
|||
|
title: {
|
|||
|
type: String,
|
|||
|
required: true
|
|||
|
},
|
|||
|
width: {
|
|||
|
type: Number,
|
|||
|
default: 240
|
|||
|
},
|
|||
|
height: {
|
|||
|
type: Number,
|
|||
|
default: 320
|
|||
|
},
|
|||
|
page: {
|
|||
|
type: String,
|
|||
|
default: null
|
|||
|
}
|
|||
|
},
|
|||
|
data() {
|
|||
|
return {
|
|||
|
ctx: null,
|
|||
|
previewUrl: '',
|
|||
|
qrCodePath: '',
|
|||
|
isLoading: false
|
|||
|
}
|
|||
|
},
|
|||
|
watch: {
|
|||
|
scene: {
|
|||
|
immediate: true,
|
|||
|
handler(val) {
|
|||
|
if (val) {
|
|||
|
this.init()
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
},
|
|||
|
methods: {
|
|||
|
async init() {
|
|||
|
if (this.isLoading) return
|
|||
|
this.isLoading = true
|
|||
|
try {
|
|||
|
// 获取小程序码
|
|||
|
await this.getWxacode()
|
|||
|
// 绘制画布
|
|||
|
await this.drawCanvas()
|
|||
|
} catch (error) {
|
|||
|
console.error('初始化小程序码失败:', error)
|
|||
|
this.$emit('error', error)
|
|||
|
uni.showToast({
|
|||
|
title: '生成小程序码失败',
|
|||
|
icon: 'none'
|
|||
|
})
|
|||
|
} finally {
|
|||
|
this.isLoading = false
|
|||
|
}
|
|||
|
},
|
|||
|
|
|||
|
// 获取小程序码
|
|||
|
async getWxacode() {
|
|||
|
try {
|
|||
|
const res = await getWxQRCode(this.page, this.scene)
|
|||
|
if (res.code === 200 && res.data) {
|
|||
|
this.qrCodePath = parseLocalUrl(res.data)
|
|||
|
} else {
|
|||
|
throw new Error('获取小程序码失败')
|
|||
|
}
|
|||
|
} catch (error) {
|
|||
|
console.error('获取小程序码失败:', error)
|
|||
|
throw error
|
|||
|
}
|
|||
|
},
|
|||
|
|
|||
|
// 绘制画布
|
|||
|
async drawCanvas() {
|
|||
|
return new Promise((resolve, reject) => {
|
|||
|
try {
|
|||
|
const ctx = uni.createCanvasContext('queueCode', this)
|
|||
|
|
|||
|
// 绘制白色背景
|
|||
|
ctx.fillStyle = '#ffffff'
|
|||
|
ctx.fillRect(0, 0, this.width, this.height)
|
|||
|
|
|||
|
// 绘制顶部大标题
|
|||
|
ctx.setFontSize(18)
|
|||
|
ctx.setFillStyle('#333333')
|
|||
|
ctx.setTextAlign('center')
|
|||
|
ctx.fillText('微信扫码', this.width/2, 30)
|
|||
|
|
|||
|
// 绘制顶部小标题
|
|||
|
ctx.setFontSize(12)
|
|||
|
ctx.setFillStyle('#666666')
|
|||
|
ctx.setTextAlign('center')
|
|||
|
ctx.fillText('扫码取号·等待叫号·叫号进场', this.width/2, 55)
|
|||
|
|
|||
|
// 下载并绘制二维码
|
|||
|
uni.downloadFile({
|
|||
|
url: this.qrCodePath,
|
|||
|
success: (res) => {
|
|||
|
const qrSize = 180
|
|||
|
const qrX = (this.width - qrSize) / 2
|
|||
|
const qrY = 70
|
|||
|
|
|||
|
// 绘制二维码
|
|||
|
ctx.drawImage(res.tempFilePath, qrX, qrY, qrSize, qrSize)
|
|||
|
|
|||
|
// 绘制底部标题背景
|
|||
|
const titleY = qrY + qrSize + 20
|
|||
|
const titleHeight = 50
|
|||
|
ctx.fillStyle = '#F8F9FA'
|
|||
|
ctx.fillRect(0, titleY, this.width, titleHeight)
|
|||
|
|
|||
|
// 绘制底部标题
|
|||
|
const fontSize = 14
|
|||
|
ctx.setFontSize(fontSize)
|
|||
|
ctx.setFillStyle('#333333')
|
|||
|
ctx.setTextAlign('center')
|
|||
|
ctx.fillText(this.title, this.width/2, titleY + titleHeight/2 + fontSize/2)
|
|||
|
|
|||
|
ctx.draw(false, () => {
|
|||
|
resolve()
|
|||
|
})
|
|||
|
},
|
|||
|
fail: (error) => {
|
|||
|
console.error('下载小程序码失败:', error)
|
|||
|
reject(error)
|
|||
|
}
|
|||
|
})
|
|||
|
} catch (error) {
|
|||
|
console.error('绘制画布失败:', error)
|
|||
|
reject(error)
|
|||
|
}
|
|||
|
})
|
|||
|
},
|
|||
|
|
|||
|
// 保存图片
|
|||
|
async save() {
|
|||
|
if (this.isLoading) {
|
|||
|
uni.showToast({
|
|||
|
title: '正在生成小程序码,请稍后',
|
|||
|
icon: 'none'
|
|||
|
})
|
|||
|
return
|
|||
|
}
|
|||
|
|
|||
|
try {
|
|||
|
// 获取用户授权
|
|||
|
const auth = await uni.authorize({
|
|||
|
scope: 'scope.writePhotosAlbum'
|
|||
|
}).catch(() => null)
|
|||
|
|
|||
|
if (!auth) {
|
|||
|
uni.showModal({
|
|||
|
title: '提示',
|
|||
|
content: '需要保存相册权限,是否去设置?',
|
|||
|
success: (res) => {
|
|||
|
if (res.confirm) {
|
|||
|
uni.openSetting()
|
|||
|
}
|
|||
|
}
|
|||
|
})
|
|||
|
return
|
|||
|
}
|
|||
|
|
|||
|
// 将画布内容保存为临时文件
|
|||
|
const res = await new Promise((resolve, reject) => {
|
|||
|
uni.canvasToTempFilePath({
|
|||
|
canvasId: 'queueCode',
|
|||
|
success: resolve,
|
|||
|
fail: reject
|
|||
|
}, this)
|
|||
|
})
|
|||
|
|
|||
|
// 保存图片到相册
|
|||
|
await uni.saveImageToPhotosAlbum({
|
|||
|
filePath: res.tempFilePath
|
|||
|
})
|
|||
|
|
|||
|
this.$emit('save')
|
|||
|
uni.showToast({
|
|||
|
title: '保存成功',
|
|||
|
icon: 'success'
|
|||
|
})
|
|||
|
} catch (error) {
|
|||
|
console.error('保存小程序码失败:', error)
|
|||
|
this.$emit('error', error)
|
|||
|
uni.showToast({
|
|||
|
title: '保存失败',
|
|||
|
icon: 'none'
|
|||
|
})
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
</script>
|
|||
|
|
|||
|
<style lang="scss" scoped>
|
|||
|
.queue-code {
|
|||
|
position: relative;
|
|||
|
display: flex;
|
|||
|
justify-content: center;
|
|||
|
align-items: center;
|
|||
|
|
|||
|
.preview-image {
|
|||
|
background-color: #ffffff;
|
|||
|
border-radius: 8rpx;
|
|||
|
}
|
|||
|
|
|||
|
canvas {
|
|||
|
background-color: #ffffff;
|
|||
|
border-radius: 8rpx;
|
|||
|
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05);
|
|||
|
}
|
|||
|
}
|
|||
|
</style>
|