自定义配置打印参数,进行打印功能
组件代码:
<--components/PrintConfig.vue -->
<template><div class="print-config"><h3>打印设置</h3><div class="config-section"><div class="config-item"><label>纸张大小:</label><select v-model="config.paperSize" @change="onPaperSizeChange"><option value="A4">A4</option><option value="A5">A5</option><option value="Letter">Letter</option><option value="Legal">Legal</option><option value="Custom">自定义</option></select></div><!-- 自定义尺寸 --><div v-if="config.paperSize === 'Custom'" class="custom-size"><div class="config-item"><label>宽度 (mm):</label><inputv-model.number="customWidth"type="number"min="50"max="500"@change="updateCustomSize"></div><div class="config-item"><label>高度 (mm):</label><inputv-model.number="customHeight"type="number"min="50"max="500"@change="updateCustomSize"></div></div><div class="config-item"><label>方向:</label><select v-model="config.orientation"><option value="portrait">纵向</option><option value="landscape">横向</option></select></div></div><div class="config-section"><h4>页边距 (mm)</h4><div class="margins-grid"><div class="margin-item"><label>上:</label><inputv-model.number="config.margins.top" type="number" min="0"max="100"></div><div class="margin-item"><label>右:</label><inputv-model.number="config.margins.right" type="number" min="0"max="100"></div><div class="margin-item"><label>下:</label><inputv-model.number="config.margins.bottom" type="number" min="0"max="100"></div><div class="margin-item"><label>左:</label><inputv-model.number="config.margins.left" type="number" min="0"max="100"></div></div></div><div class="config-section"><h4>字体设置</h4><div class="config-item"><label>字体大小:</label><select v-model="config.fontSize"><option value="10">10pt</option><option value="12">12pt</option><option value="14">14pt</option><option value="16">16pt</option><option value="18">18pt</option></select></div><div class="config-item"><label>字体:</label><select v-model="config.fontFamily"><option value="Microsoft YaHei">微软雅黑</option><option value="SimSun">宋体</option><option value="SimHei">黑体</option><option value="Arial">Arial</option><option value="Times New Roman">Times New Roman</option></select></div></div><div class="config-section"><h4>页眉页脚</h4><div class="config-item"><label>页眉:</label><input v-model="config.header" placeholder="输入页眉内容"></div><div class="config-item"><label>页脚:</label><input v-model="config.footer" placeholder="输入页脚内容"></div><div class="config-item"><label><input v-model="config.showPageNumbers" type="checkbox">显示页码</label></div></div><div class="actions"><button class="btn btn-secondary" @click="reset">重置</button><button class="btn btn-primary" @click="preview">预览</button><button class="btn btn-success" @click="print">打印</button></div></div>
</template><script lang="ts" setup>
import { usePrintStore } from '@/framework/store/modules/printStore.ts'
import { reactive, ref, watch } from 'vue'const printStore = usePrintStore()
const config = reactive({ ...printStore.printConfig })const customWidth = ref(210)
const customHeight = ref(297)const emit = defineEmits(['preview', 'print'])const onPaperSizeChange = () => {if (config.paperSize !== 'Custom') {const size = printStore.paperSizes[config.paperSize]customWidth.value = size.widthcustomHeight.value = size.height}
}const updateCustomSize = () => {printStore.paperSizes.Custom.width = customWidth.valueprintStore.paperSizes.Custom.height = customHeight.value
}const reset = () => {printStore.resetConfig()Object.assign(config, printStore.printConfig)
}const preview = () => {printStore.updateConfig('paperSize', config.paperSize)printStore.updateConfig('orientation', config.orientation)printStore.updateConfig('margins', { ...config.margins })printStore.updateConfig('fontSize', config.fontSize)printStore.updateConfig('fontFamily', config.fontFamily)printStore.updateConfig('header', config.header)printStore.updateConfig('footer', config.footer)printStore.updateConfig('showPageNumbers', config.showPageNumbers)emit('preview', { ...config })
}const print = () => {preview() // 先更新配置emit('print', { ...config })
}// 初始化自定义尺寸
watch(() => config.paperSize, onPaperSizeChange, { immediate: true })
</script><style scoped>
.print-config {padding: 20px;border: 1px solid #e0e0e0;border-radius: 8px;background: #f9f9f9;
}.config-section {margin-bottom: 20px;padding: 15px;background: white;border-radius: 6px;border: 1px solid #ddd;
}.config-section h4 {margin: 0 0 15px 0;color: #333;font-size: 14px;
}.config-item {display: flex;align-items: center;margin-bottom: 10px;
}.config-item label {width: 100px;font-size: 14px;color: #666;
}.config-item select,
.config-item input[type="text"],
.config-item input[type="number"] {flex: 1;padding: 6px 10px;border: 1px solid #ccc;border-radius: 4px;font-size: 14px;
}.custom-size {padding: 10px;background: #f5f5f5;border-radius: 4px;margin-top: 10px;
}.margins-grid {display: grid;grid-template-columns: 1fr 1fr;gap: 10px;
}.margin-item {display: flex;align-items: center;
}.margin-item label {width: 30px;font-size: 12px;
}.margin-item input {width: 80px;
}.actions {display: flex;gap: 10px;justify-content: flex-end;margin-top: 20px;
}.btn {padding: 8px 16px;border: none;border-radius: 4px;cursor: pointer;font-size: 14px;
}.btn-secondary {background: #6c757d;color: white;
}.btn-primary {background: #007bff;color: white;
}.btn-success {background: #28a745;color: white;
}.btn:hover {opacity: 0.9;
}
</style>
ts:
// stores/printStore.js
import { defineStore } from 'pinia'
import { ref, reactive } from 'vue'export const usePrintStore = defineStore('print', () => {// 打印配置const printConfig = reactive({paperSize: 'A4',orientation: 'portrait',margins: {top: 20,right: 15,bottom: 20,left: 15},fontSize: 12,fontFamily: 'Microsoft YaHei',header: '',footer: '',showPageNumbers: true})// 纸张尺寸预设const paperSizes = {'A4': { width: 210, height: 297 },'A5': { width: 148, height: 210 },'Letter': { width: 216, height: 279 },'Legal': { width: 216, height: 356 },'Custom': { width: 210, height: 297 }}// 更新配置const updateConfig = (key, value) => {if (key.includes('.')) {const keys = key.split('.')let obj = printConfigfor (let i = 0; i < keys.length - 1; i++) {obj = obj[keys[i]]}obj[keys[keys.length - 1]] = value} else {printConfig[key] = value}}// 重置配置const resetConfig = () => {Object.assign(printConfig, {paperSize: 'A4',orientation: 'portrait',margins: {top: 20,right: 15,bottom: 20,left: 15},fontSize: 12,fontFamily: 'Microsoft YaHei',header: '',footer: '',showPageNumbers: true})}return {printConfig,paperSizes,updateConfig,resetConfig}
})
utils:
// utils/printService.js
import html2canvas from 'html2canvas'
import jsPDF from 'jspdf'export class PrintService {/*** 生成打印内容*/static generatePrintContent(content, config) {const paperSize = config.paperSizes[config.paperSize]const isLandscape = config.orientation === 'landscape'const width = isLandscape ? paperSize.height : paperSize.widthconst height = isLandscape ? paperSize.width : paperSize.heightconst style = `<style>@page {size: ${config.paperSize} ${config.orientation};margin: ${config.margins.top}mm ${config.margins.right}mm ${config.margins.bottom}mm ${config.margins.left}mm;}body {margin: 0;padding: 0;font-family: ${config.fontFamily};font-size: ${config.fontSize}pt;line-height: 1.6;}.print-container {width: ${width}mm;min-height: ${height}mm;padding: 0;box-sizing: border-box;}.print-header {text-align: center;padding: 10mm 0 5mm 0;border-bottom: 1px solid #ccc;margin-bottom: 10mm;}.print-footer {text-align: center;padding: 5mm 0 10mm 0;border-top: 1px solid #ccc;margin-top: 10mm;font-size: 10pt;}.page-number {position: fixed;bottom: 10mm;right: 15mm;font-size: 10pt;color: #666;}@media print {body { margin: 0; }.no-print { display: none; }}</style>`let pageNumbers = ''if (config.showPageNumbers) {pageNumbers = `<div class="page-number">第 <span class="page"></span> 页 / 共 <span class="topage"></span> 页</div>`}return `<!DOCTYPE html><html><head><meta charset="UTF-8"><title>打印文档</title>${style}</head><body><div class="print-container">${config.header ? `<div class="print-header">${config.header}</div>` : ''}<div class="print-content">${content}</div>${config.footer ? `<div class="print-footer">${config.footer}</div>` : ''}${pageNumbers}</div></body></html>`}/*** 直接打印*/static async printDirect(content, config) {const printContent = this.generatePrintContent(content, config)const printWindow = window.open('', '_blank')printWindow.document.write(printContent)printWindow.document.close()printWindow.onload = () => {printWindow.print()printWindow.onafterprint = () => {printWindow.close()}}}/*** 导出为PDF*/static async exportToPDF(content, config, element = null) {const paperSize = config.paperSizes[config.paperSize]const isLandscape = config.orientation === 'landscape'const pdf = new jsPDF({orientation: config.orientation,unit: 'mm',format: [paperSize.width, paperSize.height]})if (element) {// 使用 html2canvas 将DOM元素转换为图片const canvas = await html2canvas(element, {scale: 2,useCORS: true,backgroundColor: '#ffffff'})const imgData = canvas.toDataURL('image/png')const imgWidth = isLandscape ? paperSize.height : paperSize.widthconst imgHeight = (canvas.height * imgWidth) / canvas.widthpdf.addImage(imgData, 'PNG', 0, 0, imgWidth, imgHeight)} else {// 使用文本内容pdf.setFont(config.fontFamily)pdf.setFontSize(config.fontSize)const margin = config.marginsconst textWidth = (isLandscape ? paperSize.height : paperSize.width) - margin.left - margin.rightconst lines = pdf.splitTextToSize(content, textWidth)let yPosition = margin.toplines.forEach(line => {if (yPosition > (isLandscape ? paperSize.width : paperSize.height) - margin.bottom) {pdf.addPage()yPosition = margin.top}pdf.text(line, margin.left, yPosition)yPosition += 10})}return pdf}/*** 打印预览*/static async preview(content, config) {const printContent = this.generatePrintContent(content, config)const previewWindow = window.open('', '_blank', 'width=800,height=600')previewWindow.document.write(printContent)previewWindow.document.close()previewWindow.document.title = '打印预览'}
}
调用组件 主页面代码:
<!-- index.vue -->
<template><div class="app"><div class="layout"><!-- 打印配置面板 --><div class="config-panel"><PrintConfig@preview="handlePreview"@print="handlePrint"/></div><!-- 内容编辑区域 --><div class="content-panel"><div class="toolbar"><h2>文档编辑</h2><button class="btn btn-primary" @click="showPreview = true">实时预览</button></div><div ref="editableContent" class="editable-content" contenteditable="true"><h1>文档标题</h1><p>这里是文档内容,您可以自由编辑...</p><p>支持富文本编辑,可以修改字体、颜色等样式。</p></div></div></div><!-- 预览模态框 --><div v-if="showPreview" class="preview-modal"><div class="preview-content"><div class="preview-header"><h3>打印预览</h3><button class="close-btn" @click="showPreview = false">×</button></div><div class="preview-body"><iframe ref="previewFrame" class="preview-frame" /></div><div class="preview-footer"><button class="btn btn-success" @click="handlePrintFromPreview">打印</button><button class="btn btn-secondary" @click="showPreview = false">关闭</button></div></div></div></div>
</template><script setup lang="ts">
import { ref, onMounted } from 'vue'
import PrintConfig from './components/PrintConfig.vue'
import { PrintService } from '@/framework/utils/printService'
import { usePrintStore } from '@/framework/store/modules/printStore'const printStore = usePrintStore()
const editableContent = ref(null)
const previewFrame = ref(null)
const showPreview = ref(false)const handlePreview = (config) => {const content = editableContent.value.innerHTMLPrintService.preview(content, { ...config, paperSizes: printStore.paperSizes })
}const handlePrint = async (config) => {const content = editableContent.value.innerHTMLtry {await PrintService.printDirect(content, { ...config, paperSizes: printStore.paperSizes })} catch (error) {console.error('打印失败:', error)alert('打印失败,请检查打印机设置')}
}const handlePrintFromPreview = () => {if (previewFrame.value && previewFrame.value.contentWindow) {previewFrame.value.contentWindow.print()}
}// 实时预览更新
onMounted(() => {// 可以添加实时预览的逻辑
})
</script><style scoped>
.app {height: 100vh;background: #f5f5f5;
}.layout {display: grid;grid-template-columns: 300px 1fr;height: 100%;
}.config-panel {background: white;border-right: 1px solid #e0e0e0;overflow-y: auto;
}.content-panel {display: flex;flex-direction: column;background: white;
}.toolbar {padding: 20px;border-bottom: 1px solid #e0e0e0;display: flex;justify-content: space-between;align-items: center;
}.editable-content {flex: 1;padding: 20px;outline: none;min-height: 500px;border: 1px solid #e0e0e0;margin: 20px;border-radius: 8px;
}.preview-modal {position: fixed;top: 0;left: 0;width: 100%;height: 100%;background: rgba(0, 0, 0, 0.5);display: flex;justify-content: center;align-items: center;z-index: 1000;
}.preview-content {background: white;width: 90%;height: 90%;border-radius: 8px;display: flex;flex-direction: column;
}.preview-header {padding: 20px;border-bottom: 1px solid #e0e0e0;display: flex;justify-content: space-between;align-items: center;
}.preview-body {flex: 1;padding: 0;
}.preview-frame {width: 100%;height: 100%;border: none;
}.preview-footer {padding: 20px;border-top: 1px solid #e0e0e0;text-align: right;
}.close-btn {background: none;border: none;font-size: 24px;cursor: pointer;color: #666;
}.btn {padding: 8px 16px;border: none;border-radius: 4px;cursor: pointer;margin-left: 10px;
}.btn-primary {background: #007bff;color: white;
}.btn-success {background: #28a745;color: white;
}.btn-secondary {background: #6c757d;color: white;
}
</style>