统一条码打印与下载的图片生成逻辑

This commit is contained in:
WuYongJie 2026-03-11 20:40:42 +08:00
parent a3e2bd9b78
commit 4791727763

View File

@ -6,7 +6,6 @@
v-for="equipment in equipmentList"
:key="equipment.equipment_code"
class="qrcode-card"
:data-export-id="equipment.id"
>
<!-- 顶部公司名称 -->
<div class="qrcode-header">
@ -44,7 +43,6 @@ import { ref, onMounted, nextTick } from 'vue'
import { getEquipmentBatchForBarcode } from '@/api/barcode'
import { useSiteStore } from '@/stores/site'
import QRCode from 'qrcode'
import html2canvas from 'html2canvas'
interface EquipmentBarcode {
id: number
@ -75,6 +73,10 @@ const siteStore = useSiteStore()
const siteName = ref('固定资产管理系统')
const equipmentList = ref<EquipmentBarcode[]>([])
const loading = ref(false)
const EXPORT_WIDTH = 500
const EXPORT_HEIGHT = 300
const EXPORT_PADDING = 12
const QR_SIZE = 176
// 格式化日期
const formatDate = (dateStr?: string) => {
@ -129,35 +131,101 @@ const generateQRCodes = async () => {
}
}
// 生成高质量图片
const loadImage = (src: string) =>
new Promise<HTMLImageElement>((resolve, reject) => {
const image = new Image()
image.onload = () => resolve(image)
image.onerror = reject
image.src = src
})
const fitText = (
ctx: CanvasRenderingContext2D,
text: string,
maxWidth: number
) => {
const value = String(text || '-')
if (ctx.measureText(value).width <= maxWidth) {
return value
}
const ellipsis = '...'
let result = value
while (result.length > 0 && ctx.measureText(`${result}${ellipsis}`).width > maxWidth) {
result = result.slice(0, -1)
}
return result ? `${result}${ellipsis}` : ellipsis
}
const renderCardToCanvas = async (equipment: EquipmentBarcode) => {
const canvas = document.createElement('canvas')
canvas.width = EXPORT_WIDTH
canvas.height = EXPORT_HEIGHT
const ctx = canvas.getContext('2d')
if (!ctx) {
throw new Error('无法创建导出画布')
}
ctx.fillStyle = '#ffffff'
ctx.fillRect(0, 0, EXPORT_WIDTH, EXPORT_HEIGHT)
ctx.fillStyle = '#000000'
ctx.textAlign = 'center'
ctx.textBaseline = 'middle'
ctx.font = '700 24px "Microsoft YaHei", "PingFang SC", Arial, sans-serif'
ctx.fillText(siteName.value, EXPORT_WIDTH / 2, 38)
ctx.lineWidth = 2
ctx.beginPath()
ctx.moveTo(EXPORT_PADDING, 66)
ctx.lineTo(EXPORT_WIDTH - EXPORT_PADDING, 66)
ctx.stroke()
const leftX = EXPORT_PADDING + 8
const infoWidth = EXPORT_WIDTH - QR_SIZE - EXPORT_PADDING * 3 - 24
const rowTop = 94
const rowGap = 56
const rows = [
equipment.assetType?.name || equipment.equipment_type || '-',
equipment.equipment_code || '-',
formatDate(equipment.created_at),
equipment.equipment_model || '-'
]
ctx.textAlign = 'left'
ctx.textBaseline = 'top'
ctx.font = '500 20px "Microsoft YaHei", "PingFang SC", Arial, sans-serif'
for (let i = 0; i < rows.length; i++) {
ctx.fillText(fitText(ctx, rows[i], infoWidth), leftX, rowTop + i * rowGap)
}
const qrDataUrl = await QRCode.toDataURL(equipment.equipment_code, {
width: QR_SIZE,
margin: 1,
color: {
dark: '#000000',
light: '#ffffff'
},
errorCorrectionLevel: 'M'
})
const qrImage = await loadImage(qrDataUrl)
const qrX = EXPORT_WIDTH - EXPORT_PADDING - QR_SIZE
const qrY = 88
ctx.drawImage(qrImage, qrX, qrY, QR_SIZE, QR_SIZE)
return canvas
}
const generateImages = async (): Promise<string[]> => {
const images: string[] = []
const cardElements = document.querySelectorAll('.qrcode-card')
for (let i = 0; i < cardElements.length; i++) {
const cardElement = cardElements[i] as HTMLElement
if (cardElement) {
const canvas = await html2canvas(cardElement, {
scale: Math.max(window.devicePixelRatio || 1, 3),
backgroundColor: '#ffffff',
useCORS: true,
logging: false,
allowTaint: false,
width: 500,
height: 300,
onclone: (clonedDocument) => {
const clonedCard = clonedDocument.querySelector(
`[data-export-id="${equipmentList.value[i].id}"]`
) as HTMLElement | null
if (clonedCard) {
clonedCard.classList.add('qrcode-card--export')
}
}
})
images.push(canvas.toDataURL('image/png', 1.0))
}
for (const equipment of equipmentList.value) {
const canvas = await renderCardToCanvas(equipment)
images.push(canvas.toDataURL('image/png', 1.0))
}
return images
}
@ -283,11 +351,6 @@ onMounted(() => {
font-family: "Microsoft YaHei", "PingFang SC", "Helvetica Neue", Arial, sans-serif;
}
.qrcode-card--export {
width: 500px !important;
height: 300px !important;
}
/* 顶部公司名称 */
.qrcode-header {
width: 100%;