Browse Source

打印:分页功能和页眉

lwx_v27^2
han2015 5 days ago
parent
commit
16df952e63
  1. 92
      src/components/DesignForm/app/index.vue
  2. 86
      src/components/DesignForm/printHtmlRender.ts
  3. 104
      src/components/DesignForm/printHtmlRender.vue
  4. 16
      src/components/DesignForm/tableListPage/formPageCont.vue
  5. 88
      src/components/DesignForm/tableListPage/index.vue
  6. 6
      src/main.ts
  7. 443
      src/views/sysworkflow/lowcodepage/appPage/appPageForm/printHtmlDom.js
  8. 4
      src/views/sysworkflow/lowcodepage/appPage/appPageForm/printSetupPage2.vue

92
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<any[]> = 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<string, any>)=>{
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"
>
<el-button
@click="printPage(scope.row)"
@click="printHtmlPage(props.formId,props.appKey,props.versionid, props.formKey,scope.row)"
type="primary"
size="small"
class="fa fa-print"

86
src/components/DesignForm/printHtmlRender.ts

@ -0,0 +1,86 @@
import {getPrintTemplate} from '@/api/DesignForm/requestapi'
import {fieldTree,PageConfig} from './printHtmlRender.vue'
import printHtmlRender from './printHtmlRender.vue'
import {printWithSmartPagination} from "@/views/sysworkflow/lowcodepage/appPage/appPageForm/printHtmlDom.js"
const printRenderTree: Ref<any[]> = 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<string, any>)=>{
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!]
}
}
}
}

104
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= ()=>{
</script>
<template>
<div class="page_brow" >
<div id="printPageAll" :style="{width: props.pageConfig.width}">
<div id="printHeader" class="page_brow">
<img height="50px" :src="logourl" />
<span v-if="props.pageConfig.pagebrow.includes('serialNumber')">表单编号:{{ props.pageConfig.serialNumber }}</span>
<span v-if="props.pageConfig.pagebrow.includes('masters_key')">表单编号: {{ props.pageConfig.masters_key }}</span>
</div>
<div id="printContainer" :style="{width: props.pageConfig.width}" >
<div id="printContainer">
<h3>{{props.name}}</h3>
<div style="display: flex;justify-content: space-between; margin-bottom: 10px;">
<span v-if="props.pageConfig.pagebrow.includes('founder')">创建人:{{ props.pageConfig.founder }}</span>
@ -93,14 +100,23 @@ const generateQrCode= ()=>{
<div class="text_area">{{ group.field }}</div>
</div>
<div v-else-if="group.type=='table'" class="section_table" v-if="group.checked!=2">
<h5>{{ group.name }}</h5>
<div class="bder_table" >
<el-table class="table" :data="group.data" border >
<template v-for="child in group.child">
<el-table-column :prop="child.field" :label="child.name" v-if="child.checked!=2" />
</template>
</el-table>
</div>
<table class="bder_table">
<caption >{{ group.name }}</caption>
<thead>
<tr>
<template v-for="child in group.child">
<th v-if="child.checked!=2" >{{ child.name }}</th>
</template>
</tr>
</thead>
<tbody>
<tr v-for="(dd, index) in group.data" :key="dd">
<template v-for="child in group.child">
<td v-if="child.checked!=2" >{{ dd[child.field]}}</td>
</template>
</tr>
</tbody>
</table>
</div>
<div v-else-if="group.type=='tabs'" class="section_tabs" v-if="group.checked!=2">
<h5>{{ group.name }}</h5>
@ -124,13 +140,22 @@ const generateQrCode= ()=>{
</div>
<div v-else-if="tabs.type=='table'" class="section" v-if="tabs.checked!=2">
<h5>{{ tabs.name }}</h5>
<div class="bder_table" >
<el-table class="table" border >
<template v-for="child in tabs.child">
<el-table-column :prop="child.field" :label="child.name" v-if="child.checked!=2" />
</template>
</el-table>
</div>
<table class="bder_table">
<thead>
<tr>
<template v-for="child in tabs.child">
<th v-if="child.checked!=2" >{{ child.name }}</th>
</template>
</tr>
</thead>
<tbody>
<tr v-for="(dd, index) in tabs.data" :key="dd">
<template v-for="child in tabs.child">
<td v-if="child.checked!=2" >{{ dd[child.field]}}</td>
</template>
</tr>
</tbody>
</table>
</div>
<div v-else class="tabs_cell_box" v-if="tabs.checked!=2">
<span class="box_name">
@ -143,9 +168,8 @@ const generateQrCode= ()=>{
</div>
</div>
<div v-else v-if="group.checked!=2">{{ group }}</div>
</div>
<div v-if="props.pageConfig.qrcode" style="display: flex; justify-content: space-between; ">
<div v-if="props.pageConfig.qrcode" style="display: flex; justify-content: space-between;margin-top: 20px; ">
{{ generateQrCode() }}
<div style="display: inherit; align-items: center;">
<img id="qrcode" width="100px" :src="qrdata"/>
@ -157,6 +181,7 @@ const generateQrCode= ()=>{
</div>
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
#printContainer{
@ -242,7 +267,7 @@ h5{
//////////////
.tabs_item {
display: inline-flex;
min-height: 40px;
min-height: 30px;
min-width: 50%; /* 默认最小宽度 */
margin-right: -1px;/* 解决边框重合问题*/
/* 默认情况:没有 title/section 时宽度 50% */
@ -273,16 +298,20 @@ h5{
display: inline-flex;
.box_name {
padding: 8px 2px;
text-align: center;
align-content: center;
width: 30%;
min-height: 30px;
text-wrap: wrap;
max-height: 80px;
overflow: hidden; /* 配合 ellipsis 需要 */
}
.content {
padding: 8px 2px;
text-align: center;
align-content: center;
width: 70%;
min-height: 30px;
text-wrap: wrap;
word-break: break-all;
border-left: 1px solid rgb(182, 181, 181);
@ -291,14 +320,27 @@ h5{
.tabs_cell_box{
width: 100%;
}
</style>
<style>
#printContainer table{
display: block;
border: 0px solid;
border-spacing: 1px;
border-collapse: collapse;
overflow-y: visible !important;
}
#printContainer thead{
border: 1px solid rgb(182, 181, 181);
}
#printContainer table caption{
text-align: center;
caption-side: top;
color: black;
font-weight: bold;
width: 100%;
margin: 5px;
}
#printContainer .el-table th.el-table__cell{
background: none !important;
@ -315,9 +357,15 @@ h5{
font-weight: normal;
border-right: 1px solid rgb(182, 181, 181);
text-align: center;
line-height: 28px;
}
#printContainer table td{
border: 1px solid rgb(182, 181, 181);
line-height: 28px;
}
.el-scrollbar__view {
display: block !important;
width: 100% !important;
}
</style>

16
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<any>();
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) {
<el-header height="30px">
<el-icon @click="closeDrawer"><Close /></el-icon>
</el-header>
<el-button type="primary" style="margin:-19px 0px 10px;" v-if="gainTaskFormInfoData" @click="onPrintFrom()" >打印</el-button>
<ak-form
ref="formEl"
v-loading="formLoading"

88
src/components/DesignForm/tableListPage/index.vue

@ -39,9 +39,7 @@ import { softDeletion, retractRunWorkFlow, recalSendMsg } from "@/api/taskapi/ma
import { echatsViews } from "@/api/DesignForm/types";
import { formatNumber } from "@/api/DesignForm/utils";
import { Ref } from "vue";
import printHtmlRender from '../printHtmlRender.vue'
import {fieldTree,PageConfig} from '../printHtmlRender.vue'
import {printElement} from "@/views/sysworkflow/lowcodepage/appPage/appPageForm/printHtmlDom.js"
import {printHtmlPage} from '../printHtmlRender.ts'
//
import FormPageCont from "@/components/DesignForm/tableListPage/formPageCont.vue";
import TableFlow from "@/views/sysworkflow/lowcodepage/pageFlow/tableFlow.vue";
@ -1344,88 +1342,6 @@ const lookPageInfo = (val: any) => {
lookPageInfoIsShow.value = true;
};
/****************表单打印功能************************/
const printRenderMode = ref(false);
const printRenderTree: Ref<any[]> = ref([]);
const printPage = async (row: any) => {
let data:any[]=[]
let _pageConfig:PageConfig
let title:string="表单";
//appkeyformkey
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<string, any>)=>{
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"
>
<el-button
@click="printPage(scope.row)"
@click="printHtmlPage(props.formId,props.appKey,props.versionid, props.formKey,scope.row)"
type="primary"
size="small"
class="fa fa-print"

6
src/main.ts

@ -30,8 +30,6 @@ import * as pinia from './store/index'
import SketchRule from 'vue3-sketch-ruler'
import 'vue3-sketch-ruler/lib/style.css'
import {hiPrintPlugin} from "vue-plugin-hiprint"
@ -51,10 +49,10 @@ app.directive('focus', {
}
});
hiPrintPlugin.disAutoConnect();
//hiPrintPlugin.disAutoConnect();
app.use(router).use(i18n).use(ComComponents).use(ComWidget).use(ElementPlus, {
locale: zhCn
}).use(hiPrintPlugin).use(AKDesign).use(pinia.store).mount('#app');
}).use(AKDesign).use(pinia.store).mount('#app');

443
src/views/sysworkflow/lowcodepage/appPage/appPageForm/printHtmlDom.js

@ -1,73 +1,414 @@
export function printElement(elementId) {
/**
*
* 智能分页打印解决方案 - 标准表格版支持 caption
* 新增表格分页时保留 caption 在第一页
*
*/
export function printWithSmartPagination(elementId, options = {}) {
const {
paperSize = 'A4',
pageHeight= 0,
margin = 15,
debug = false,
landscape = false,
repeatHeader = true
} = options;
const element = document.getElementById(elementId);
if (!element) {
console.error('元素未找到');
console.error('❌ 未找到元素:', elementId);
return;
}
// 克隆元素,避免影响原页面
const printContent = element.cloneNode(true);
console.log('📄 开始分析页面结构...');
const structure = analyzePageStructure(element);
const pages = paginateContent(structure, { paperSize,pageHeight, margin, landscape, repeatHeader });
console.log(`✅ 分页完成,共 ${pages.length}`);
const printContent = buildPrintPages(pages, structure, { repeatLogo: true });
printViaIframe(printContent, { paperSize, margin, debug, landscape });
}
function analyzePageStructure(container) {
const header = container.querySelector('#printHeader');
const mainContent = container.querySelector('#printContainer');
return {
hasHeader: !!header,
hasBottom: false,
header: header?.cloneNode(true),
bottom: "",
main: mainContent,
tables: Array.from(mainContent.querySelectorAll('table'))
};
}
function paginateContent(structure, options) {
const { paperSize,pageHeight, margin,landscape } = options;
let _pageH=pageHeight
//处理页高的问题
if(pageHeight==0){
if(landscape){
_pageH = {
'A5': 148,'A4': 210, 'A3': 297
}[paperSize];
}else{
_pageH = {
'A5': 210,'A4': 297, 'A3': 420
}[paperSize];
}
}
const paperHeight = _pageH * 3.78;
const marginPx = margin * 3.78;
const printableHeight = paperHeight - marginPx * 2;
// 创建隐藏iframe
const headerHeight = structure.hasHeader ? structure.header.getBoundingClientRect().height : 0;
const bottomHeight = structure.hasBottom ? structure.bottom.getBoundingClientRect().height : 0;
const availableHeight = printableHeight - headerHeight - bottomHeight-30; //50是页眉与内容的间距,
console.log(`📊 分页基础参数:`);
console.log(` 纸张高度: ${paperHeight.toFixed(0)}px, 可用高度: ${availableHeight.toFixed(0)}px`);
const pages = [];
let currentPage = [];
let currentHeight = 0;
Array.from(structure.main.children).forEach((child, index) => {
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<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(`
<!DOCTYPE html>
<html>
<head>
<title>打印</title>
<style>
/* 打印优化样式 */
body { margin: 20px; font-size: 14px; }
@media print {
body { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
}
#printContainer table{
display: block;
border: 0px solid;
border-spacing: 1px;
border-collapse: collapse;
}
#printContainer .el-table th.el-table__cell{
background: none !important;
}
#printContainer table th{
border-right: 1px solid;
}
#printContainer table td{
border: 1px solid;
}
/* 复制原页面样式 */
${Array.from(document.styleSheets)
.map(sheet => {
try {
return Array.from(sheet.cssRules).map(rule => rule.cssText).join('\n');
} catch (e) {
return '';
}
})
.join('\n')}
</style>
<meta charset="UTF-8">
<title>检验日报打印</title>
<style>${allStyles}</style>
</head>
<body style="background:white !important;">
${printContent.outerHTML}
<body>
${content.outerHTML}
${debug ? '<div style="position: fixed; top: 10px; right: 10px; background: red; color: white; padding: 10px; z-index: 9999;">🔍 调试模式</div>' : ''}
</body>
</html>
`);
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');
}

4
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)=>{
<el-checkbox label="创建人" value="founder" />
<el-checkbox label="创建时间" value="founderTime" />
<el-checkbox label="所属部门" value="deptOrg" />
<el-checkbox label="表单编号" value="serialNumber" />
<el-checkbox label="表单编号" value="masters_key" />
</el-checkbox-group>
<el-checkbox style="margin-left: 22px;" v-model="objPageConfig.qrcode" label="表单二维码" />
<span>业务字段</span>

Loading…
Cancel
Save