From 16df952e63bd0792f396c21cfbecec0452762ab0 Mon Sep 17 00:00:00 2001 From: han2015 <1019850453@qq.com> Date: Thu, 4 Dec 2025 15:46:05 +0800 Subject: [PATCH] =?UTF-8?q?=E6=89=93=E5=8D=B0=EF=BC=9A=E5=88=86=E9=A1=B5?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E5=92=8C=E9=A1=B5=E7=9C=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/DesignForm/app/index.vue | 92 +--- src/components/DesignForm/printHtmlRender.ts | 86 ++++ src/components/DesignForm/printHtmlRender.vue | 104 ++-- .../DesignForm/tableListPage/formPageCont.vue | 16 +- .../DesignForm/tableListPage/index.vue | 88 +--- src/main.ts | 6 +- .../appPage/appPageForm/printHtmlDom.js | 443 ++++++++++++++++-- .../appPage/appPageForm/printSetupPage2.vue | 4 +- 8 files changed, 577 insertions(+), 262 deletions(-) create mode 100644 src/components/DesignForm/printHtmlRender.ts diff --git a/src/components/DesignForm/app/index.vue b/src/components/DesignForm/app/index.vue index 19bbb36..1070dd7 100644 --- a/src/components/DesignForm/app/index.vue +++ b/src/components/DesignForm/app/index.vue @@ -43,10 +43,8 @@ import { } from "@/api/taskapi/management"; import { formatNumber } from "@/api/DesignForm/utils"; -import printHtmlRender from '../printHtmlRender.vue' -import {fieldTree,PageConfig} from '../printHtmlRender.vue' -import {printElement} from "@/views/sysworkflow/lowcodepage/appPage/appPageForm/printHtmlDom.js" -import {getPrintTemplate} from '@/api/DesignForm/requestapi' +import {printHtmlPage} from '../printHtmlRender.ts' + //引入组件 import FormPageCont from "@/components/DesignForm/tableListPage/formPageCont.vue"; import TableFlow from "@/views/sysworkflow/lowcodepage/pageFlow/appTableFlow.vue"; @@ -1556,90 +1554,6 @@ const lookPageInfo = (val: any) => { lookPageInfoIsShow.value = true; }; - -/****************表单打印功能************************/ -const printRenderMode = ref(false); -const printRenderTree: Ref = ref([]); -const printPage = async (row: any) => { - let data:any[]=[] - let _pageConfig:PageConfig - let title:string="表单"; - await getPrintTemplate({"versionid":props.versionid,"formkey":props.appKey}).then(resp=>{ - title=resp.data.title - if(resp.data.formtemplatejson!=""){ - data=JSON.parse(resp.data.formtemplatejson) - }else{ - alert("请先创建打印模板!") - return - } - - if(resp.data.pageconfigjson!=""){ - _pageConfig=JSON.parse(resp.data.pageconfigjson) - if(_pageConfig.founder!=""){ - _pageConfig.founder=row[_pageConfig.founder] - } - if(_pageConfig.founderTime!=""){ - _pageConfig.founderTime=row[_pageConfig.founderTime] - } - if(_pageConfig.deptOrg!=""){ - _pageConfig.deptOrg=row[_pageConfig.deptOrg] - } - if(_pageConfig.serialNumber!=""){ - _pageConfig.serialNumber=row[_pageConfig.serialNumber] - } - } - }) - - let qrstr=`https://wab.hxgk.group/#/form_table/taskInfo?id=${props.formId}&key=${props.appKey}& - formid=${props.versionid}&formKey=${props.formKey}&state=2` - data.forEach(node=>{ - deepLoopForm(node,row) - }) - printRenderTree.value=data - printRenderMode.value = true; - ElMessageBox({ - message: () => h('div',{style:{ width:'1200px',display:'flex','flex-direction':'column'}},[ - h(ElButton, { - type:"primary", - style: "margin:10px 10px 5px auto;", - onClick: () => { - printElement("printContainer") - } - },'打印表单'), - h('div',{style:{ border: '1px solid black', width: 'fit-content', margin: '5px','align-self': 'center'}},[ - h(printHtmlRender,{ - name:title, - fieldTree:printRenderTree.value, - pageConfig:_pageConfig, - qrcode:qrstr, - }) - ]) - ]), - showConfirmButton:false, - customStyle: { '--el-messagebox-width':'1300px',padding:'10px'}, - }).then(() => { - }) -}; - - -const deepLoopForm=(node:fieldTree, row: Record)=>{ - if(Array.isArray(node)){ - node.forEach(item=>{deepLoopForm(item,row)}) - return - } - - if(node.field!=""){ - let rnode:Object; - if (row.hasOwnProperty(node.field!)){// 有这个字段 - if (node.type=="table" || node.type=="tabs"){ - node.data=row[node.field!] - }else{ - node.field=row[node.field!] - } - } - } - } - /** @ 作者: 秦东 @ 时间: 2024-04-05 11:29:50 @@ -2300,7 +2214,7 @@ const isObject = (obj) => { placement="top-end" > = ref([]); +export const printHtmlPage = async (pformid:string,pappkey:string,pversionid:string,pformkey:string,row: any) => { + let data:any[]=[] + let _pageConfig:PageConfig + let title:string="表单"; + await getPrintTemplate({"versionid":pversionid,"formkey":pappkey}).then(resp=>{ + title=resp.data.title + if(resp.data.formtemplatejson!=""){ + data=JSON.parse(resp.data.formtemplatejson) + }else{ + alert("请先创建打印模板!") + return + } + + if(resp.data.pageconfigjson!=""){ + _pageConfig=JSON.parse(resp.data.pageconfigjson) + if(_pageConfig.founder!=""){ + _pageConfig.founder=row[_pageConfig.founder] + } + if(_pageConfig.founderTime!=""){ + _pageConfig.founderTime=row[_pageConfig.founderTime] + } + if(_pageConfig.deptOrg!=""){ + _pageConfig.deptOrg=row[_pageConfig.deptOrg] + } + _pageConfig.masters_key=row.masters_key + } + }) + + let qrstr=`https://wab.hxgk.group/#/form_table/taskInfo?id=${pformid}&key=${pappkey}& + formid=${pversionid}&formKey=${pformkey}&state=2` + data.forEach(node=>{ + deepLoopForm(node,row) + }) + printRenderTree.value=data + + ElMessageBox({ + message: () => h('div',{style:{ width:'1200px',display:'flex','flex-direction':'column'}},[ + h(ElButton, { + type:"primary", + style: "margin:10px 10px 5px auto;", + onClick: () => { + printWithSmartPagination("printPageAll",{ + paperSize: _pageConfig.pagesize, + landscape: _pageConfig.horizontal=="htal"? true : false , //默认竖向 + margin: 5, + }); + } + },'打印表单'), + h('div',{style:{ border: '1px solid black', width: 'fit-content', margin: '5px','align-self': 'center'}},[ + h(printHtmlRender,{ + name:title, + fieldTree:printRenderTree.value, + pageConfig:_pageConfig, + qrcode:qrstr, + }) + ]) + ]), + showConfirmButton:false, + customStyle: { '--el-messagebox-width':'1300px',padding:'10px'}, + }).then(() => { + }) +}; + +const deepLoopForm=(node:fieldTree, row: Record)=>{ + if(Array.isArray(node)){ + node.forEach(item=>{deepLoopForm(item,row)}) + return + } + + if(node.field!=""){ + let rnode:Object; + if (row.hasOwnProperty(node.field!)){// 有这个字段 + if (node.type=="table" || node.type=="tabs"){ + node.data=row[node.field!] + }else{ + node.field=row[node.field!] + } + } + } + } \ No newline at end of file diff --git a/src/components/DesignForm/printHtmlRender.vue b/src/components/DesignForm/printHtmlRender.vue index 0775997..0c8316b 100644 --- a/src/components/DesignForm/printHtmlRender.vue +++ b/src/components/DesignForm/printHtmlRender.vue @@ -23,7 +23,7 @@ export interface PageConfig{ founder:string; founderTime:string; deptOrg:string; - serialNumber:string; + masters_key:string; qrcode:boolean; } @@ -38,8 +38,14 @@ const qrdata=ref("") const userName=useUserStore().nickname; function parseDataPicker(val:string){ - if(val==""|| val.match(/[a-z]/) ) return val; - let str=new Date(parseInt(val)).toISOString() + let str:string; + if(typeof(val)=="number"){ + str=new Date(val).toISOString() + }else{ + if(val==""|| val.match(/[a-z]/) ) return val; + str=new Date(parseInt(val)).toISOString() + } + return str.slice(0,10)+" "+str.slice(11,16) } @@ -64,11 +70,12 @@ const generateQrCode= ()=>{ diff --git a/src/components/DesignForm/tableListPage/formPageCont.vue b/src/components/DesignForm/tableListPage/formPageCont.vue index 132a0c8..86d93dd 100644 --- a/src/components/DesignForm/tableListPage/formPageCont.vue +++ b/src/components/DesignForm/tableListPage/formPageCont.vue @@ -35,7 +35,7 @@ import { constFormBtnEvent, constFormProps } from "@/api/DesignForm/utils"; import RunFlowStep from "@/views/taskplatform/taskmanagement/runFlowStep.vue"; import { customerFormVersionCont } from "@/api/taskapi/types"; -import { stringify } from "uuid"; +import {printHtmlPage} from '../printHtmlRender.ts' const props = defineProps({ isShow: { @@ -59,7 +59,7 @@ const props = defineProps({ default() { return {}; }, - }, + } }); const formEl = ref(); const emits = defineEmits(["update:isShow", "getPageData", "optionsValue4Get4"]); @@ -221,6 +221,17 @@ interface TreeNode { children: TreeNode[]; } +function onPrintFrom(){ + printHtmlPage( + gainTaskFormInfoData.structure.cfid.toString(), + formState.formData.config.groupKey, + gainTaskFormInfoData.structure.id.toString(), + gainTaskFormInfoData.structure.tablekey, + gainTaskFormInfoData.tableData + ) +} + + function mapIdsToLabels(treeNodes: TreeNode[], ids1: string): string[] { /* console.log(treeNodes) console.log(ids1) */ @@ -526,6 +537,7 @@ function optionsValue3Get3(data: any, fieldName: any) { + 打印 { lookPageInfoIsShow.value = true; }; -/****************表单打印功能************************/ -const printRenderMode = ref(false); -const printRenderTree: Ref = ref([]); -const printPage = async (row: any) => { - let data:any[]=[] - let _pageConfig:PageConfig - let title:string="表单"; - //这里appkey和formkey 有些乱,由于历史遗留问题,不好区分。。。 - await getPrintTemplate({"versionid":props.versionid,"formkey":props.appKey}).then(resp=>{ - title=resp.data.title - if(resp.data.formtemplatejson!=""){ - data=JSON.parse(resp.data.formtemplatejson) - } - if(resp.data.pageconfigjson!=""){ - _pageConfig=JSON.parse(resp.data.pageconfigjson) - if(_pageConfig.founder!=""){ - _pageConfig.founder=row[_pageConfig.founder] - } - if(_pageConfig.founderTime!=""){ - _pageConfig.founderTime=row[_pageConfig.founderTime] - } - if(_pageConfig.deptOrg!=""){ - _pageConfig.deptOrg=row[_pageConfig.deptOrg] - } - if(_pageConfig.serialNumber!=""){ - _pageConfig.serialNumber=row[_pageConfig.serialNumber] - } - } - }) - - let qrstr=`https://wab.hxgk.group/#/form_table/taskInfo?id=${props.formId}&key=${props.appKey}& - formid=${props.versionid}&formKey=${props.formKey}&state=2` - - data.forEach(node=>{ - deepLoopForm(node,row) - }) - printRenderTree.value=data - printRenderMode.value = true; - ElMessageBox({ - message: () => h('div',{style:{ width:'1200px',display:'flex','flex-direction':'column'}},[ - h(ElButton, { - type:"primary", - style: "margin:10px 10px 5px auto;", - onClick: () => { - printElement("printContainer") - } - },'打印表单'), - h('div',{style:{ border: '1px solid black', width: 'fit-content', margin: '5px','align-self': 'center'}},[ - h(printHtmlRender,{ - name:title, - fieldTree:printRenderTree.value, - pageConfig:_pageConfig, - qrcode:qrstr - }) - ]) - ]), - showConfirmButton:false, - customStyle: { '--el-messagebox-width':'1300px',padding:'10px'}, - }).then(() => { - }) - -}; - - -const deepLoopForm=(node:fieldTree, row: Record)=>{ - if(Array.isArray(node)){ - node.forEach(item=>{deepLoopForm(item,row)}) - return - } - - if(node.field!=""){ - let rnode:Object; - if (row.hasOwnProperty(node.field!)){// 有这个字段 - if (node.type=="table" || node.type=="tabs"){ - node.data=row[node.field!] - }else{ - node.field=row[node.field!] - } - } - } - } - /** @ 作者: 秦东 @ 时间: 2024-04-05 11:29:50 @@ -2102,7 +2018,7 @@ const isObject = (obj: any) => { placement="top-end" > { + const childHeight = child.getBoundingClientRect().height; + const elementInfo = `${child.tagName}${child.className ? '.' + child.className.split(' ')[0] : ''}`; + const isTableContainer = child.querySelector('table'); + const remainingHeight = availableHeight - currentHeight; + + console.log(`\n🔍 [元素 ${index + 1}] ${elementInfo}`); + console.log(` 高度: ${childHeight.toFixed(1)}px, 累积: ${currentHeight.toFixed(1)}px`); + console.log(` 剩余空间: ${remainingHeight.toFixed(1)}px`); + + // ==================== 智能表格分割 ==================== + if (isTableContainer) { + if (currentHeight + childHeight <= availableHeight * 0.92) { + console.log(` ✅ 表格能完整放入当前页`); + currentPage.push(child.cloneNode(true)); + currentHeight += childHeight; + } else { + console.log(` ⚠️ 表格无法完整放入,剩余空间: ${remainingHeight.toFixed(1)}px`); + + // 对表格进行智能分页,第一页使用剩余空间 + const tablePages = paginateTableSmart(child, remainingHeight, availableHeight, options); + + if (tablePages.length > 0) { + // 第一页:部分表格 + 前面累积的普通元素(合并在一起) + const firstPageContent = tablePages[0]; + if (firstPageContent.length > 0) { + currentPage.push(...firstPageContent); + pages.push([...currentPage]); + console.log(` ✅ 创建混合页: ${currentPage.length}个元素 (含caption和部分表格)`); + } + + // 剩余的表格页(纯表格) + for (let i = 1; i < tablePages.length; i++) { + pages.push(tablePages[i]); + console.log(` 📊 添加纯表格页 ${i} (${tablePages[i].length}个元素)`); + } + } + + currentPage = []; + currentHeight = 0; + } + return; + } + // ============================================================ + + // 普通元素处理 + if (currentHeight + childHeight > availableHeight * 0.92) { + if (currentPage.length > 0) { + console.log(` 📄 推入当前页 (${currentPage.length}个元素)`); + pages.push([...currentPage]); + currentPage = []; + currentHeight = 0; + } + } + + currentPage.push(child.cloneNode(true)); + currentHeight += (childHeight+20); //20margin + }); + + // 处理剩余元素 + if (currentPage.length > 0) { + console.log(`\n📄 最后推入剩余元素 (${currentPage.length}个)`); + pages.push([...currentPage]); + } + + return pages; +} + +/** + * 智能表格分页函数(支持 caption) + * @returns {Array} 分页结果,每个元素是一个页面数组 + */ +function paginateTableSmart(tableContainer, firstPageHeight, subsequentPageHeight, options) { + const table = tableContainer.querySelector('table'); + const caption = table?.querySelector('caption'); // 获取 caption + const thead = table?.querySelector('thead'); + const tbody = table?.querySelector('tbody'); + + if (!thead || !tbody) { + console.warn('⚠️ 表格缺少 thead 或 tbody'); + return [[tableContainer.cloneNode(true)]]; + } + + const captionHeight = caption ? caption.getBoundingClientRect().height : 0; + const headerHeight = thead.getBoundingClientRect().height; + const rows = Array.from(tbody.querySelectorAll('tr')); + + console.log(` 📊 智能分页参数:`); + console.log(` caption高度: ${captionHeight.toFixed(1)}px, 表头高度: ${headerHeight.toFixed(1)}px`); + console.log(` 第一页可用: ${firstPageHeight.toFixed(1)}px, 后续页可用: ${subsequentPageHeight.toFixed(1)}px`); + console.log(` 总行数: ${rows.length}行`); + + // 如果整个表格能在第一页放下 + const totalTableHeight = tableContainer.getBoundingClientRect().height; + if (totalTableHeight <= firstPageHeight * 0.85) { + console.log(` ✅ 表格总高度 ${totalTableHeight.toFixed(1)}px 能放入第一页`); + // 包含 caption + return [[createTablePage(tableContainer, thead, rows, true)]]; + } + + const pages = []; + let currentRows = []; + let currentHeight = captionHeight + headerHeight; // 包含 caption 高度 + let isFirstPage = true; + + rows.forEach((row, rowIndex) => { + const rowHeight = row.getBoundingClientRect().height; + const pageHeight = isFirstPage ? firstPageHeight : subsequentPageHeight; + + // 检查是否需要分页 + if (currentHeight + rowHeight > pageHeight * 0.85 && currentRows.length > 0) { + // 创建当前页(只有第一页包含 caption) + pages.push([createTablePage(tableContainer, thead, currentRows, isFirstPage)]); + console.log(` 📊 分页${isFirstPage ? '(含caption)' : ''}: ${currentRows.length}行, ${currentHeight.toFixed(1)}px`); + + // 重置为新页面 + currentRows = [row]; + currentHeight = captionHeight + headerHeight + rowHeight; // 新页面也要包含 caption 高度 + isFirstPage = false; + } else { + currentRows.push(row); + currentHeight += rowHeight; + } + + // 最后一行 + if (rowIndex === rows.length - 1 && currentRows.length > 0) { + pages.push([createTablePage(tableContainer, thead, currentRows, isFirstPage)]); + console.log(` 📊 最后分页${isFirstPage ? '(含caption)' : ''}: ${currentRows.length}行, ${currentHeight.toFixed(1)}px`); + } + }); + + console.log(` 📊 表格总计分页: ${pages.length}页`); + return pages; +} + +/** + * 创建表格页 + * @param {boolean} includeCaption - 是否包含 caption(只有第一页为 true) + */ +function createTablePage(originalContainer, thead, rows, includeCaption = false) { + const container = originalContainer.cloneNode(false); + const table = document.createElement('table'); + + const originalTable = originalContainer.querySelector('table'); + table.className = originalTable.className; + table.style.cssText = originalTable.style.cssText; + + // 只在第一页添加 caption + if (includeCaption) { + const caption = originalTable.querySelector('caption'); + if (caption) { + table.appendChild(caption.cloneNode(true)); + } + } + + table.appendChild(thead.cloneNode(true)); + + const tbody = document.createElement('tbody'); + rows.forEach(row => tbody.appendChild(row.cloneNode(true))); + table.appendChild(tbody); + + container.appendChild(table); + return container; +} + +function buildPrintPages(pages, structure, options) { + const container = document.createElement('div'); + container.className = 'print-report-container'; + + pages.forEach((pageElements, index) => { + const pageDiv = document.createElement('div'); + pageDiv.className = 'print-page'; + + const elements = Array.isArray(pageElements) ? pageElements : [pageElements]; + + if (options.repeatLogo && structure.hasHeader) { + const headerClone = structure.header.cloneNode(true); + headerClone.style.borderBottom = '2px solid rgb(182, 181, 181)'; + headerClone.style.marginBottom = '20px'; + pageDiv.appendChild(headerClone); + } + + const contentDiv = document.createElement('div'); + contentDiv.className = 'print-page-content'; + + elements.forEach(el => { + if (el?.nodeType === Node.ELEMENT_NODE) { + contentDiv.appendChild(el); + } + }); + + pageDiv.appendChild(contentDiv); + + if (index === pages.length - 1 && structure.hasBottom) { + const bottomClone = structure.bottom.cloneNode(true); + bottomClone.style.marginTop = '30px'; + bottomClone.style.borderTop = '1px solid #ccc'; + bottomClone.style.paddingTop = '15px'; + pageDiv.appendChild(bottomClone); + } + + container.appendChild(pageDiv); + }); + + return container; +} + +function printViaIframe(content, options) { + const { paperSize, margin, debug,landscape } = options; const iframe = document.createElement('iframe'); iframe.style.display = 'none'; document.body.appendChild(iframe); - + const iframeDoc = iframe.contentDocument || iframe.contentWindow.document; - // 写入样式和内容 + let originalStyles = ''; + try { + originalStyles = collectAllStyles(); + if (debug) console.log('📄 已收集原始样式,长度:', originalStyles.length); + } catch (e) { + console.warn('⚠️ 收集样式时出错:', e); + } + + const orientation = landscape ? 'landscape' : 'portrait'; + const printStyles = ` + @page { + size: ${paperSize.toLowerCase()} ${orientation}; + margin: ${margin}mm; + } + body { + margin: 0; + padding: 0; + font-family: "SimSun", "Microsoft YaHei", serif; + background: white; + } + .print-page { + page-break-after: always; + padding:${margin}mm ${margin+5}mm; + } + .print-page:last-child { + page-break-after: avoid; + } + table { + width: 100% !important; + border-collapse: collapse !important; + } + th, td { + border: 1px solid rgb(182, 181, 181) !important; + page-break-inside: avoid !important; + text-align: center; + line-height: 30px; + } + thead { + display: table-header-group !important; + } + table caption{ + text-align: center; + caption-side: top; + color: black; + font-weight: bold; + margin: 5px; + } + h3{ + width: 100%; + font-size: 25px; + font-weight: bold; + text-align:center; + } + + h5{ + font-weight: bold; + width: 100%; + margin: 5px; + text-align:center; + } + .page_brow{ + padding: 0 !important; + display: flex; + justify-content: space-between; + align-items: center; + } + `; + + const allStyles = `${originalStyles}\n${printStyles}`; + iframeDoc.open(); iframeDoc.write(` + - 打印 - + + 检验日报打印 + - - ${printContent.outerHTML} + + ${content.outerHTML} + ${debug ? '
🔍 调试模式
' : ''} `); iframeDoc.close(); - - // 触发打印 - iframe.onload = function() { - iframe.contentWindow.print(); - // 延迟移除iframe - setTimeout(() => document.body.removeChild(iframe), 1000); + + iframe.onload = () => { + setTimeout(() => { + if (debug) { + iframe.style.display = 'block'; + iframe.style.position = 'fixed'; + iframe.style.inset = '0'; + iframe.style.width = '100%'; + iframe.style.height = '100%'; + iframe.style.zIndex = '99999'; + iframe.style.background = 'white'; + console.log('✅ 调试模式已开启'); + } else { + iframe.contentWindow.print(); + setTimeout(() => iframe.remove(), 10000); + } + }, 300); }; +} + +function collectAllStyles() { + const styles = []; + Array.from(document.styleSheets).forEach(sheet => { + try { + const rules = Array.from(sheet.cssRules).map(r => r.cssText).join('\n'); + if (rules) styles.push(rules); + } catch (e) { + console.warn('⚠️ 无法读取样式表:', e); + } + }); + document.querySelectorAll('style').forEach(style => { + if (style.textContent) styles.push(style.textContent); + }); + return styles.join('\n'); } \ No newline at end of file diff --git a/src/views/sysworkflow/lowcodepage/appPage/appPageForm/printSetupPage2.vue b/src/views/sysworkflow/lowcodepage/appPage/appPageForm/printSetupPage2.vue index 8e6100e..dc4c1c5 100644 --- a/src/views/sysworkflow/lowcodepage/appPage/appPageForm/printSetupPage2.vue +++ b/src/views/sysworkflow/lowcodepage/appPage/appPageForm/printSetupPage2.vue @@ -27,7 +27,7 @@ const pageSize=ref('A4') const pageDret=ref('vtal') //定义页眉页脚的数据,取自pageConfig的字段 -const systemType:string[]=["founder","founderTime","deptOrg","serialNumber"] +const systemType:string[]=["founder","founderTime","deptOrg"] const props = defineProps({ @@ -321,7 +321,7 @@ const updateNodeData=(val:fieldTree,val2:boolean,val3:boolean)=>{ - + 业务字段