diff --git a/frontend/src/components/BarcodePrint.vue b/frontend/src/components/BarcodePrint.vue index 61b2d23..bf76ea0 100644 --- a/frontend/src/components/BarcodePrint.vue +++ b/frontend/src/components/BarcodePrint.vue @@ -6,7 +6,6 @@ v-for="equipment in equipmentList" :key="equipment.equipment_code" class="qrcode-card" - :data-export-id="equipment.id" >
@@ -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([]) 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((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 => { 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%;