work-order/work-order-uniapp/components/QueueCode.vue
2025-07-27 20:34:15 +08:00

253 lines
6.2 KiB
Vue
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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>