统一条码打印与下载的图片生成逻辑
This commit is contained in:
parent
a3e2bd9b78
commit
4791727763
@ -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%;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user