Browse Source
# Conflicts: # src/components/DesignForm/app/index.vue # src/components/DesignForm/printHtmlRender.vue # src/components/DesignForm/tableListPage/index.vue # src/views/sysworkflow/lowcodepage/appPage/appPageForm/printSetupPage2.vueqin_27
19 changed files with 3108 additions and 319 deletions
File diff suppressed because it is too large
@ -0,0 +1,120 @@ |
|||
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" |
|||
import {gainRunTaskFlow} from "@/api/DesignForm/requestapi"; |
|||
import { useOrgMemberStore } from "@/store/modules/orgMember"; |
|||
|
|||
const printRenderTree: Ref<any[]> = ref([]); |
|||
const orgMembers = useOrgMemberStore() |
|||
export const printHtmlPage = async (pformid:string,pappkey:string,pversionid:string,pformkey:string,row: any) => { |
|||
let data:any[]=[] |
|||
let _pageConfig:PageConfig |
|||
let title:string="表单"; |
|||
let flowlist:any[]=[] |
|||
|
|||
if (row.flowIsOpens==1){ |
|||
let sendInfo = { |
|||
id:row.runFlowId |
|||
} |
|||
await gainRunTaskFlow(sendInfo) |
|||
.then((resp:any) =>{ |
|||
//console.log("获取流程--werwerwerwer--->",resp.data)
|
|||
flowlist=resp.data.flowList |
|||
}) |
|||
} |
|||
|
|||
let noTemplate:boolean=false |
|||
await getPrintTemplate({"versionid":pversionid,"formkey":pappkey}).then(resp=>{ |
|||
title=resp.data.title |
|||
if(resp.data.formtemplatejson!=""){ |
|||
data=JSON.parse(resp.data.formtemplatejson) |
|||
}else{ |
|||
noTemplate=true |
|||
alert("请先创建打印模板!") |
|||
return |
|||
} |
|||
|
|||
if(resp.data.pageconfigjson!=""){ |
|||
_pageConfig=JSON.parse(resp.data.pageconfigjson) |
|||
if(_pageConfig.founder!=""){ |
|||
_pageConfig.founder=row[_pageConfig.founder] |
|||
_pageConfig.deptOrg=orgMembers.listMap[row.createrOrg] |
|||
} |
|||
|
|||
if(_pageConfig.founderTime!=""){ |
|||
_pageConfig.founderTime=row[_pageConfig.founderTime] |
|||
} |
|||
|
|||
_pageConfig.masters_key=row.masters_key |
|||
} |
|||
}) |
|||
|
|||
if(noTemplate) return; |
|||
|
|||
let qrstr=`https://wab.hxgk.group/#/form_table/taskListPage?id=${pformid}&key=${pappkey}&
|
|||
formid=${pversionid}&formKey=${pformkey}&qrDetailId=${row.id}` |
|||
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, |
|||
flowList:flowlist, |
|||
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.type=="tabs"&&node.child){ |
|||
node.child?.forEach(item=>{deepLoopForm(item,row)}) |
|||
return |
|||
} |
|||
|
|||
if(node.field!=""){ |
|||
if (row.hasOwnProperty(node.field!)){// 有这个字段
|
|||
if (node.type=="table"){ |
|||
node.data=row[node.field!] |
|||
}else if(node.type=="select"||node.type=="checkbox"){ |
|||
const val=row[node.field!] |
|||
node.field="" |
|||
for(let op of node.options!){ |
|||
if(op.value==val){ |
|||
node.field=op.label |
|||
break; |
|||
} |
|||
} |
|||
}else{ |
|||
node.field=row[node.field!] |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,344 @@ |
|||
<!-- |
|||
@ 作者: han2015 |
|||
@ 时间: 2025-05-12 15:39:13 |
|||
@ 备注: 文档管理组件 |
|||
--> |
|||
<script lang="ts" setup> |
|||
import { useUserStore } from "@/store/modules/user"; |
|||
import { getMatterList,getMySpaces} from "@/api/doc/index" |
|||
import { matterPage,matterInfo,matterTree,matterPermit} from "@/api/doc/type" |
|||
import Space from "./space.vue"; |
|||
import {ElSelect,ElOption, ElText,ElInput,ElTree,TreeNode,ElDropdown,ElDropdownItem} from "element-plus"; |
|||
import { doAccessManage,getSpaceMatterList,doCreateSpaceDir,doDelSpaceMatter, |
|||
doAiTraining,doAiDocDels,spaceMatterRename} from "@/api/doc/space" |
|||
|
|||
//定义事件 |
|||
defineProps({ |
|||
modelValue: String // 接收父组件的值 |
|||
}) |
|||
const emit = defineEmits(['update:modelValue']) |
|||
|
|||
const userStore = useUserStore(); |
|||
const uid=btoa("p0"+userStore.userInfoCont.userId); |
|||
const rawUid="p0"+userStore.userInfoCont.userId |
|||
|
|||
const matterList = ref<matterInfo[]>([]) |
|||
const treeData=ref<matterTree[]>([])//{name:'个人空间',uuid:'root',children:[]} |
|||
const currentNode=ref<matterTree>({}) //打开的路径层次 |
|||
const paginInfo = ref({ page: 0, total: 0 }) |
|||
|
|||
const SpaceID= ref<{name:string,uuid:string,userUuid:string,manager:boolean,permits:matterPermit}>({}) //当前space的id |
|||
const spaceEleRef = ref() //space组件的引用,它与spaceTreeRef没有父子关系,反而是为了处理spaceTree的操作而创建的该变量 |
|||
const spaceTreeRef = ref(); //space的树组件的引用 |
|||
let spaceNodeUid="" //用来判断树组件的展开和关闭,如何只是展开和关闭的点击事件不在刷新,通currentNode的作用 |
|||
let isNewSpaceNode=true //用来减少父组件树的重置 |
|||
const spaceCutLevelPermit=ref(0) |
|||
|
|||
const Departs = computed(() => { |
|||
return `${'p0'+userStore.userInfoCont.userId},${userStore.userInfoCont.company},${userStore.userInfoCont.department},${userStore.userInfoCont.organization}` |
|||
}) |
|||
|
|||
enum PERMITS { |
|||
FORBID, //0 |
|||
VIEW, //1 |
|||
DOWNLOAD, //2 |
|||
UPLOAD, //3 |
|||
UPANDDOWNLOAD, //4 |
|||
EDIT, //5 |
|||
MANAGER, //6 |
|||
} |
|||
|
|||
//加载目录文件列表 |
|||
function onLoadMatterList(){ |
|||
emit('update:modelValue', currentNode.value.uuid) |
|||
let _page: matterPage = { |
|||
page: paginInfo.value.page, |
|||
pageSize: 50, |
|||
orderCreateTime: "DESC", |
|||
orderDir: "DESC", |
|||
dir:"true", |
|||
puuid:currentNode.value.uuid, |
|||
deleted:false |
|||
}; |
|||
|
|||
getMatterList(uid,_page).then((resp)=>{ |
|||
//page+1 是由于分页的起始index是1,而后端api的分页index起始是0 |
|||
paginInfo.value={total:resp.data.totalPages,page:resp.data.page} |
|||
matterList.value=resp.data.data |
|||
}) |
|||
} |
|||
//树节点展开 |
|||
function onNodeExpand(node: TreeNode, resolve: (data: matterTree[]) => void, reject: () => void) { |
|||
let cuuid ="root" |
|||
if ((node.data as matterTree).uuid) { |
|||
cuuid= (node.data as matterTree).uuid |
|||
currentNode.value = node.data |
|||
} |
|||
|
|||
let _page: matterPage = { |
|||
page: 0, |
|||
pageSize: 50, |
|||
orderCreateTime: "DESC", |
|||
orderDir: "DESC", |
|||
dir:"true", |
|||
puuid: cuuid, |
|||
deleted: false |
|||
}; |
|||
|
|||
getMatterList(uid, _page).then((resp) => { |
|||
let node_data = resp.data.data.filter((item) => { |
|||
return item.dir |
|||
}).map((val) => { |
|||
const copy = structuredClone(val) |
|||
copy.dir = !copy.dir |
|||
return copy |
|||
}) |
|||
|
|||
resolve(node_data) |
|||
}).catch(() => reject()) |
|||
emit('update:modelValue', currentNode.value.uuid) |
|||
} |
|||
//树节点单击 |
|||
function onNodeClick(data:matterTree,node:TreeNode,self:any,env:any){ |
|||
if (data){ |
|||
if (currentNode.value.uuid === data.uuid) return; |
|||
currentNode.value = data |
|||
}else{ |
|||
currentNode.value={uuid:"root",name:"个人空间"} |
|||
} |
|||
onLoadMatterList() |
|||
} |
|||
|
|||
//-------------------space feature--------------------- |
|||
function onSpaceNodeClick(data:matterTree,node:TreeNode,self:any,env:any){ |
|||
//如果在单个组件上重复点击,不在刷新请求 |
|||
if(spaceNodeUid==data.uuid) return; |
|||
spaceNodeUid=data.uuid |
|||
|
|||
if(data.uuid.startsWith("s0") && data.uuid!=SpaceID.value.uuid){ //切换空间 |
|||
currentNode.value={uuid:"root",name:data.name}//切换空间就要重置currentNode |
|||
SpaceID.value={ |
|||
name: data.name ?? "", |
|||
uuid: data.uuid ?? "", |
|||
userUuid: data.userUuid ?? "", |
|||
manager: data.manager ?? false, |
|||
permits:data.permits ?? { id: 0, MatterUuid: '', data: '{}' } |
|||
}; |
|||
onSpaceMatterList() |
|||
}else{ |
|||
let matter= { |
|||
uuid:data.uuid==SpaceID.value.uuid?"root":data.uuid, |
|||
puuid: data.puuid, |
|||
name:data.name, |
|||
agent:data.agent, |
|||
dir:true, |
|||
permits:data.permits, |
|||
path:data.path, |
|||
permitVal:data.permitVal |
|||
} |
|||
//打开具体的节点 |
|||
handleSpaceDoubleClick(matter) |
|||
} |
|||
} |
|||
|
|||
function flushSpaceTree(uuid:string,data:matterTree[]){ |
|||
if(uuid==="root") uuid=SpaceID.value.uuid |
|||
spaceTreeRef.value.updateKeyChildren(uuid,data) |
|||
} |
|||
|
|||
function handleSpaceDoubleClick(row:matterInfo,ind?:number){ |
|||
if(row.dir){ |
|||
//设置当前文件夹的权限等级 |
|||
if(row.permitVal){ |
|||
spaceCutLevelPermit.value=row.permitVal |
|||
}else{ |
|||
if (SpaceID.value.manager) { |
|||
spaceCutLevelPermit.value=9 |
|||
}else{ |
|||
let _pert: Record<string, number> |
|||
_pert = JSON.parse(row.permits!.data) |
|||
let val=_pert[rawUid.replace("p0","")] |
|||
if(val){ |
|||
spaceCutLevelPermit.value = val |
|||
}else{ |
|||
spaceCutLevelPermit.value = PERMITS.FORBID //没有权限!! |
|||
} |
|||
} |
|||
} |
|||
|
|||
|
|||
isNewSpaceNode=true |
|||
//1:如果是当前目录的父组件没必要更新目录树 |
|||
//2: 如果当前目录是当前空间根目录,没必要更新目录树 |
|||
if(currentNode.value.puuid==row.uuid || row.uuid=="root") isNewSpaceNode=false |
|||
currentNode.value=row |
|||
onSpaceMatterList() |
|||
} |
|||
} |
|||
|
|||
//加载目录文件列表 |
|||
function onSpaceMatterList(){ |
|||
emit('update:modelValue', currentNode.value.uuid) |
|||
let _page: matterPage= { |
|||
page: paginInfo.value.page, |
|||
pageSize: 50, |
|||
orderCreateTime: "DESC", |
|||
orderDir: "DESC", |
|||
puuid:currentNode.value.uuid, |
|||
dir:"true", |
|||
deleted:false, |
|||
space:SpaceID.value.uuid, |
|||
}; |
|||
|
|||
getSpaceMatterList(uid,_page).then((resp)=>{ |
|||
if (SpaceID.value.manager) { |
|||
resp.data.data.forEach(item => { |
|||
item.permitVal=PERMITS.MANAGER |
|||
}) |
|||
matterList.value=resp.data.data |
|||
}else{ |
|||
matterList.value=resp.data.data.filter(item=>{ //具体的权限验证和过滤 |
|||
|
|||
if(item.permits.ID==0){ |
|||
item.permitVal=spaceCutLevelPermit.value |
|||
}else{ |
|||
//如果该文档有设定权限,解析该文档的具体权限 |
|||
let _pert: Record<string, number> |
|||
_pert = JSON.parse(item.permits!.data) |
|||
let val=_pert[rawUid.replace("p0","")] |
|||
if(val){ |
|||
item.permitVal = val |
|||
}else{ |
|||
item.permitVal = PERMITS.FORBID //没有权限!! |
|||
} |
|||
} |
|||
|
|||
if(item.permitVal>0){ |
|||
return true |
|||
} |
|||
|
|||
return false |
|||
}) |
|||
} |
|||
|
|||
|
|||
//展开的时候暂时不做目录更新,看以后的使用情况 |
|||
let node_data = matterList.value.filter(item=> { |
|||
return item.dir |
|||
}).map(val => { |
|||
const copy = structuredClone(toRaw(val)) |
|||
copy.dir = !copy.dir |
|||
copy.manager=SpaceID.value.manager |
|||
return copy |
|||
}) |
|||
|
|||
if(isNewSpaceNode) { |
|||
//由于支持目录树的原因,所有的空间根目录uuid都是root,这样树组件就有问题,所以用了spaceid作为其uuid |
|||
//但是云盘的根目录是root,所以当uuid是spaceid时,自动替换为root |
|||
if(currentNode.value.uuid=="root") flushSpaceTree(SpaceID.value.uuid,node_data); |
|||
else flushSpaceTree(currentNode.value.uuid,node_data); |
|||
} |
|||
}) |
|||
} |
|||
|
|||
/////////////////////////////////////////////////////// |
|||
|
|||
|
|||
//渲染完页面再执行 |
|||
onMounted(async () => { |
|||
currentNode.value={uuid:"root",name:"个人空间"} |
|||
//加载我的空间列表 |
|||
await getMySpaces(uid,{roles:Departs.value}).then((resp)=>{ |
|||
resp.data.forEach((item)=>{ |
|||
let ismanager=false |
|||
if(item.userUuid==rawUid || item.managers.includes(rawUid)) ismanager=true; |
|||
spaceTreeRef.value.append({name:item.name,uuid:item.uuid,dir:false,userUuid:item.userUuid,manager:ismanager,permits:item.permits}) |
|||
}) |
|||
}) |
|||
emit('update:modelValue', currentNode.value.uuid) |
|||
}); |
|||
|
|||
</script> |
|||
|
|||
<template> |
|||
<p> <span style="font-size:20px;font-weight: bold;">请选择目标节点:</span> 当前节点 > {{ currentNode.name }}</p> |
|||
<div class="menus_tree"> |
|||
<div class="area_header"> |
|||
<el-icon :size="20"><House /></el-icon><span class="area_name" > 个人空间</span> |
|||
</div> |
|||
<el-tree |
|||
ref="treeRef" |
|||
style="max-width: 600px" |
|||
:data="treeData" |
|||
node-key="uuid" |
|||
highlight-current |
|||
lazy |
|||
:load="onNodeExpand" |
|||
:props="{label: 'name',children:'children',isLeaf:'dir'}" |
|||
:default-expanded-keys="['root']" |
|||
@node-click="onNodeClick" |
|||
> |
|||
<template #default="{ node, data }"> |
|||
<div class="tree-item"> |
|||
<span>{{ node.label }}</span> |
|||
</div> |
|||
</template> |
|||
</el-tree> |
|||
<div class="area_header"> |
|||
<el-icon :size="20"><Collection /></el-icon> <span class="area_name" > 共享空间</span> |
|||
</div> |
|||
<el-tree |
|||
ref="spaceTreeRef" |
|||
style="max-width: 600px" |
|||
node-key="uuid" |
|||
accordion |
|||
highlight-current |
|||
lazy |
|||
:props="{label: 'name',children:'children',isLeaf:'dir'}" |
|||
@node-click="onSpaceNodeClick" |
|||
> |
|||
<template #default="{ node, data }"> |
|||
<div class="tree-item"> |
|||
<span>{{ node.label}}</span> |
|||
</div> |
|||
</template> |
|||
</el-tree> |
|||
</div> |
|||
|
|||
</template> |
|||
|
|||
<style lang="scss" scoped> |
|||
.menus_tree{ |
|||
display: flex; |
|||
flex-direction: column; |
|||
height:550px; |
|||
overflow-y: scroll; |
|||
.el-tree{ |
|||
--el-color-primary-light-9:#6eb3f8; |
|||
--el-tree-node-hover-bg-color:#a1c7ee; |
|||
--el-tree-node-content-height:43px; |
|||
--el-tree-expand-icon-color:#4c4c4e; |
|||
} |
|||
} |
|||
|
|||
.area_header{ |
|||
display: flex; |
|||
margin-top: 9px; |
|||
padding: 5px 0; |
|||
background-color: white; |
|||
align-items: center; |
|||
.area_name{ |
|||
align-content: center; |
|||
margin-left: 8px; |
|||
color: #686854; |
|||
font-weight: bold; |
|||
} |
|||
} |
|||
|
|||
</style> |
|||
|
|||
<style lang="scss"> |
|||
.el-message-box__message{ |
|||
width: 100%; |
|||
} |
|||
</style> |
|||
@ -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'); |
|||
} |
|||
Loading…
Reference in new issue