|
|
|
@ -37,7 +37,7 @@ function analyzePageStructure(container) { |
|
|
|
return { |
|
|
|
hasHeader: !!header, |
|
|
|
hasBottom: false, |
|
|
|
header: header?.cloneNode(true), |
|
|
|
header: header, |
|
|
|
bottom: "", |
|
|
|
main: mainContent, |
|
|
|
tables: Array.from(mainContent.querySelectorAll('table')) |
|
|
|
@ -61,78 +61,65 @@ function paginateContent(structure, options) { |
|
|
|
} |
|
|
|
|
|
|
|
const paperHeight = _pageH * 3.78; |
|
|
|
|
|
|
|
const marginPx = margin * 3.78; |
|
|
|
const printableHeight = paperHeight - marginPx * 2; |
|
|
|
|
|
|
|
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是页眉与内容的间距,
|
|
|
|
|
|
|
|
const availableHeight = printableHeight - headerHeight - bottomHeight; //50是页眉与内容的间距,
|
|
|
|
|
|
|
|
console.log(`📊 分页基础参数:`); |
|
|
|
console.log(` 纸张高度: ${paperHeight.toFixed(0)}px, 可用高度: ${availableHeight.toFixed(0)}px`); |
|
|
|
|
|
|
|
const pages = []; |
|
|
|
let currentPage = []; |
|
|
|
let currentHeight = 0; |
|
|
|
const ElementMarginPx=10; |
|
|
|
|
|
|
|
Array.from(structure.main.children).forEach((child, index) => { |
|
|
|
const childHeight = child.getBoundingClientRect().height; |
|
|
|
if (childHeight.toFixed(1)==0) return; |
|
|
|
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) { |
|
|
|
// 普通元素处理
|
|
|
|
if (currentHeight + childHeight > availableHeight) { |
|
|
|
if (currentPage.length > 0 && (childHeight<=100 || remainingHeight<=100)){ |
|
|
|
console.log(` 📄 推入当前页 (${currentPage.length}个元素)`); |
|
|
|
pages.push(currentPage); |
|
|
|
currentPage = []; |
|
|
|
currentHeight = 0; |
|
|
|
}else{ |
|
|
|
// //child 本身比较大,currentPage还有比较大的空白
|
|
|
|
const result=splitElement(child, remainingHeight,availableHeight,ElementMarginPx) |
|
|
|
const subPages=result.pages |
|
|
|
if (subPages.length > 0) { |
|
|
|
// 第一页:部分表格 + 前面累积的普通元素(合并在一起)
|
|
|
|
const firstPageContent = tablePages[0]; |
|
|
|
const firstPageContent = subPages[0]; |
|
|
|
if (firstPageContent.length > 0) { |
|
|
|
currentPage.push(...firstPageContent); |
|
|
|
pages.push([...currentPage]); |
|
|
|
console.log(` ✅ 创建混合页: ${currentPage.length}个元素 (含caption和部分表格)`); |
|
|
|
pages.push(currentPage); |
|
|
|
console.log(` ✅ 创建混合页: ${currentPage.length}个元素`); |
|
|
|
} |
|
|
|
|
|
|
|
// 剩余的表格页(纯表格)
|
|
|
|
for (let i = 1; i < tablePages.length; i++) { |
|
|
|
pages.push(tablePages[i]); |
|
|
|
console.log(` 📊 添加纯表格页 ${i} (${tablePages[i].length}个元素)`); |
|
|
|
if(subPages.length==1) return |
|
|
|
// 剩余的分页,直到倒数第二页
|
|
|
|
for (let i = 1; i < subPages.length-1; i++) { |
|
|
|
pages.push(subPages[i]); |
|
|
|
console.log(` 📊 添加普通页 ${i} (${subPages[i].length}个元素)`); |
|
|
|
} |
|
|
|
|
|
|
|
currentPage = subPages[subPages.length-1]; //继续使用最后一页,可能有大片的空间
|
|
|
|
currentHeight = result.curHight; //智能表格分页后最后一页的高度(可能包含 caption 和部分表格)
|
|
|
|
} |
|
|
|
|
|
|
|
currentPage = []; |
|
|
|
currentHeight = 0; |
|
|
|
return; |
|
|
|
} |
|
|
|
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
|
|
|
|
currentPage.push(child.cloneNode(true)); |
|
|
|
currentHeight += (childHeight+ElementMarginPx); //20margin
|
|
|
|
}); |
|
|
|
|
|
|
|
// 处理剩余元素
|
|
|
|
@ -140,23 +127,232 @@ function paginateContent(structure, options) { |
|
|
|
console.log(`\n📄 最后推入剩余元素 (${currentPage.length}个)`); |
|
|
|
pages.push([...currentPage]); |
|
|
|
} |
|
|
|
|
|
|
|
return pages; |
|
|
|
} |
|
|
|
|
|
|
|
function splitElement(element, remainingHeight, availableHeight,elementMarginPx) { |
|
|
|
console.log(` 🔪 开始递归探测可拆分内容,上一页剩余高度: ${remainingHeight}px`); |
|
|
|
|
|
|
|
// 递归查找可拆分的容器
|
|
|
|
const findSplittableContainer = (el, depth = 0) => { |
|
|
|
// 检查当前元素的高度是否小于100px
|
|
|
|
const elHeight = el.getBoundingClientRect().height; |
|
|
|
if (elHeight <= 105 ) { |
|
|
|
console.log(` 元素高度 ${elHeight.toFixed(1)}px < 100px,停止拆分,返回元素本身`); |
|
|
|
return { |
|
|
|
container: el, |
|
|
|
items: [el], // 将自身作为唯一项返回
|
|
|
|
level: depth, |
|
|
|
isMinimumSize: true // 标记为最小尺寸元素
|
|
|
|
}; |
|
|
|
} |
|
|
|
|
|
|
|
if (el.tagName=="table" || el.tagName=="TABLE"){ |
|
|
|
return { |
|
|
|
container: el, |
|
|
|
items: [el], // 将自身作为唯一项返回
|
|
|
|
level: depth, |
|
|
|
istable: true // 标记为最小尺寸元素
|
|
|
|
}; |
|
|
|
} |
|
|
|
|
|
|
|
const children = Array.from(el.children); |
|
|
|
// 如果当前层有多个子元素,就在这里拆分
|
|
|
|
if (children.length > 1) { |
|
|
|
console.log(` 在第${depth}层找到${children.length}个子元素,在此拆分`); |
|
|
|
return { |
|
|
|
container: el, |
|
|
|
items: children, |
|
|
|
level: depth |
|
|
|
}; |
|
|
|
} |
|
|
|
|
|
|
|
// 如果只有一个子元素,继续向下探测
|
|
|
|
if (children.length === 1) { |
|
|
|
return findSplittableContainer(children[0], depth + 1); |
|
|
|
} |
|
|
|
|
|
|
|
// 没有子元素,无法拆分
|
|
|
|
return null; |
|
|
|
} |
|
|
|
|
|
|
|
// 获取元素在布局中的垂直增量(对当前页高度的贡献)
|
|
|
|
const getVerticalIncrement = (item, previousItem) => { |
|
|
|
const itemRect = item.getBoundingClientRect(); |
|
|
|
|
|
|
|
// 如果没有前一个元素,或者这是新行的第一个元素,返回完整高度
|
|
|
|
if (!previousItem) { |
|
|
|
return itemRect.height; |
|
|
|
} |
|
|
|
|
|
|
|
const prevRect = previousItem.getBoundingClientRect(); |
|
|
|
|
|
|
|
// 判断是否在同一行(垂直位置相近)
|
|
|
|
const isSameRow = Math.abs(itemRect.top - prevRect.top) < 10; |
|
|
|
|
|
|
|
if (isSameRow) { |
|
|
|
// 同一行:不增加高度(因为已经在同一行的高度中了)
|
|
|
|
return 0; |
|
|
|
} else { |
|
|
|
// 不同行:返回新行的高度
|
|
|
|
return itemRect.height+3; |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
// // 找到可拆分的容器
|
|
|
|
const splittable = findSplittableContainer(element); |
|
|
|
// 按高度拆分这些项
|
|
|
|
const pages = []; // 存储所有页的元素数组
|
|
|
|
let currentPage = []; // 当前页的元素
|
|
|
|
let currentHeight = 0; // 当前页的总高度
|
|
|
|
let lastItemInPage = null; // 当前页最后一个元素,用于判断同行
|
|
|
|
|
|
|
|
// 首先处理上一页的剩余空间
|
|
|
|
if (remainingHeight > 0) { |
|
|
|
console.log(` ⬆️ 尝试填充上一页剩余高度: ${remainingHeight}px`); |
|
|
|
|
|
|
|
// 找出能放入上一页的元素
|
|
|
|
let remainingItems = [...splittable.items]; |
|
|
|
let itemsForPreviousPage = []; |
|
|
|
let heightForPreviousPage = 0; |
|
|
|
let lastItemForPrevious = null; |
|
|
|
|
|
|
|
for (let i = 0; i < remainingItems.length; i++) { |
|
|
|
const item = remainingItems[i]; |
|
|
|
const verticalIncrement = getVerticalIncrement(item, lastItemForPrevious); |
|
|
|
|
|
|
|
// 如果加上这个元素的垂直增量不会超出剩余高度,就加入上一页
|
|
|
|
if (heightForPreviousPage + verticalIncrement <= remainingHeight) { |
|
|
|
itemsForPreviousPage.push(item.cloneNode(true)); |
|
|
|
heightForPreviousPage += verticalIncrement; |
|
|
|
lastItemForPrevious = item; |
|
|
|
remainingItems.shift(); // 移除已处理的元素
|
|
|
|
i--; // 调整索引
|
|
|
|
} else { |
|
|
|
if (item.tagName=="table" || item.tagName=="TABLE") { |
|
|
|
const remain=remainingHeight-heightForPreviousPage |
|
|
|
console.log(` ⚠️ 表格无法上一页剩余空间,剩余空间: ${remainingHeight.toFixed(1)}px`); |
|
|
|
|
|
|
|
// 对表格进行智能分页,第一页使用剩余空间
|
|
|
|
const result = paginateTableSmart(item, remain, availableHeight,elementMarginPx, null); |
|
|
|
const tablePages=result.pages |
|
|
|
if (tablePages.length > 0) { |
|
|
|
// 第一页:部分表格 + 前面累积的普通元素(合并在一起)
|
|
|
|
const firstPageContent = tablePages[0]; |
|
|
|
if (firstPageContent.length > 0) { |
|
|
|
itemsForPreviousPage.push(...firstPageContent); |
|
|
|
pages.push(itemsForPreviousPage); |
|
|
|
console.log(` ✅ 创建混合页: ${itemsForPreviousPage.length}个元素 (含caption和部分表格)`); |
|
|
|
itemsForPreviousPage=[] //立刻清理掉,后面有对非table的处理
|
|
|
|
} |
|
|
|
|
|
|
|
if(tablePages.length>1){ |
|
|
|
// 剩余的表格页(纯表格)
|
|
|
|
for (let i = 1; i < tablePages.length-1; i++) { |
|
|
|
pages.push(tablePages[i]); |
|
|
|
console.log(` 📊 添加纯表格页 ${i} (${tablePages[i].length}个元素)`); |
|
|
|
} |
|
|
|
|
|
|
|
currentPage = tablePages[tablePages.length-1]; //继续使用最后一页,可能有大片的空间
|
|
|
|
currentHeight = result.curHight; //智能表格分页后最后一页的高度(可能包含 caption 和部分表格)
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
remainingItems.shift(); // 移除已处理的元素
|
|
|
|
splittable.items = remainingItems; |
|
|
|
} |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 如果有元素放入上一页
|
|
|
|
if (itemsForPreviousPage.length > 0) { |
|
|
|
pages.push(itemsForPreviousPage); |
|
|
|
console.log(` ⬆️ 填充上一页: ${itemsForPreviousPage.length}项,高度 ${heightForPreviousPage.toFixed(1)}px`); |
|
|
|
|
|
|
|
// 剩余的元素继续处理
|
|
|
|
splittable.items = remainingItems; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 处理剩余的元素,分页
|
|
|
|
splittable.items.forEach((item, index) => { |
|
|
|
// 计算这个元素对当前页高度的垂直增量
|
|
|
|
const verticalIncrement = getVerticalIncrement(item, lastItemInPage); |
|
|
|
|
|
|
|
// 如果当前页不为空,且加上这个元素的垂直增量会超高,则开始新页
|
|
|
|
if (currentHeight + verticalIncrement > availableHeight) { |
|
|
|
if (item.tagName=="table" || item.tagName=="TABLE") { |
|
|
|
const remain=availableHeight-currentHeight |
|
|
|
console.log(` ⚠️ 表格无法上一页剩余空间,剩余空间: ${remainingHeight.toFixed(1)}px`); |
|
|
|
|
|
|
|
// 对表格进行智能分页,第一页使用剩余空间
|
|
|
|
const result = paginateTableSmart(item, remain, availableHeight,elementMarginPx, null); |
|
|
|
const tablePages=result.pages |
|
|
|
if (tablePages.length > 0) { |
|
|
|
// 第一页:部分表格 + 前面累积的普通元素(合并在一起)
|
|
|
|
const firstPageContent = tablePages[0]; |
|
|
|
if (firstPageContent.length > 0) { |
|
|
|
currentPage.push(...firstPageContent); |
|
|
|
pages.push(currentPage); |
|
|
|
console.log(` ✅ 创建混合页: ${currentPage.length}个元素 (含caption和部分表格)`); |
|
|
|
} |
|
|
|
|
|
|
|
if(tablePages.length==1) return; |
|
|
|
// 剩余的表格页(纯表格)
|
|
|
|
for (let i = 1; i < tablePages.length-1; i++) { |
|
|
|
pages.push(tablePages[i]); |
|
|
|
console.log(` 📊 添加纯表格页 ${i} (${tablePages[i].length}个元素)`); |
|
|
|
} |
|
|
|
|
|
|
|
currentPage = tablePages[tablePages.length-1]; //继续使用最后一页,可能有大片的空间
|
|
|
|
currentHeight = result.curHight; //智能表格分页后最后一页的高度(可能包含 caption 和部分表格)
|
|
|
|
} |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
// 保存当前页
|
|
|
|
pages.push(currentPage); |
|
|
|
console.log(` 📄 第 ${pages.length} 页: ${currentPage.length}项,高度 ${currentHeight.toFixed(1)}px`); |
|
|
|
|
|
|
|
// 开始新页 - 当前元素作为新页的第一个元素
|
|
|
|
currentPage = [item.cloneNode(true)]; |
|
|
|
currentHeight = item.getBoundingClientRect().height; // 新页第一个元素贡献完整高度
|
|
|
|
lastItemInPage = item; |
|
|
|
} else { |
|
|
|
// 加入当前页
|
|
|
|
currentPage.push(item.cloneNode(true)); |
|
|
|
currentHeight += verticalIncrement; // 只增加垂直方向上的增量
|
|
|
|
lastItemInPage = item; |
|
|
|
} |
|
|
|
}); |
|
|
|
// 处理最后一个元素
|
|
|
|
pages.push(currentPage); |
|
|
|
|
|
|
|
console.log(` 📄 第 ${pages.length} 页: ${currentPage.length}项,高度 ${currentHeight.toFixed(1)}px`); |
|
|
|
console.log(` ✅ 递归探测拆分完成,共 ${pages.length} 页`); |
|
|
|
|
|
|
|
// 返回所有页的数据
|
|
|
|
return { |
|
|
|
pages: pages, |
|
|
|
curHight: currentHeight, // 最后一页的高度
|
|
|
|
}; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* 智能表格分页函数(支持 caption) |
|
|
|
* @returns {Array<Array>} 分页结果,每个元素是一个页面数组 |
|
|
|
*/ |
|
|
|
function paginateTableSmart(tableContainer, firstPageHeight, subsequentPageHeight, options) { |
|
|
|
const table = tableContainer.querySelector('table'); |
|
|
|
function paginateTableSmart(tableContainer, firstPageHeight, subsequentPageHeight,ElementMarginPx, 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)]]; |
|
|
|
return {pages:[tableContainer.cloneNode(true)],curHight:tableContainer.getBoundingClientRect().height}; |
|
|
|
} |
|
|
|
|
|
|
|
const captionHeight = caption ? caption.getBoundingClientRect().height : 0; |
|
|
|
@ -170,10 +366,10 @@ function paginateTableSmart(tableContainer, firstPageHeight, subsequentPageHeigh |
|
|
|
|
|
|
|
// 如果整个表格能在第一页放下
|
|
|
|
const totalTableHeight = tableContainer.getBoundingClientRect().height; |
|
|
|
if (totalTableHeight <= firstPageHeight * 0.85) { |
|
|
|
if (totalTableHeight <= firstPageHeight) { |
|
|
|
console.log(` ✅ 表格总高度 ${totalTableHeight.toFixed(1)}px 能放入第一页`); |
|
|
|
// 包含 caption
|
|
|
|
return [[createTablePage(tableContainer, thead, rows, true)]]; |
|
|
|
return {pages:[createTablePage(tableContainer, thead, rows, true)],curHight:totalTableHeight}; |
|
|
|
} |
|
|
|
|
|
|
|
const pages = []; |
|
|
|
@ -186,18 +382,18 @@ function paginateTableSmart(tableContainer, firstPageHeight, subsequentPageHeigh |
|
|
|
const pageHeight = isFirstPage ? firstPageHeight : subsequentPageHeight; |
|
|
|
|
|
|
|
// 检查是否需要分页
|
|
|
|
if (currentHeight + rowHeight > pageHeight * 0.85 && currentRows.length > 0) { |
|
|
|
if (currentHeight + rowHeight > pageHeight && 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 高度
|
|
|
|
currentHeight = captionHeight + headerHeight + rowHeight+ElementMarginPx; // 新页面也要包含 caption 高度
|
|
|
|
isFirstPage = false; |
|
|
|
} else { |
|
|
|
currentRows.push(row); |
|
|
|
currentHeight += rowHeight; |
|
|
|
currentHeight += rowHeight+ElementMarginPx; |
|
|
|
} |
|
|
|
|
|
|
|
// 最后一行
|
|
|
|
@ -208,7 +404,7 @@ function paginateTableSmart(tableContainer, firstPageHeight, subsequentPageHeigh |
|
|
|
}); |
|
|
|
|
|
|
|
console.log(` 📊 表格总计分页: ${pages.length}页`); |
|
|
|
return pages; |
|
|
|
return {pages:pages,curHight:currentHeight}; |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
@ -219,7 +415,7 @@ function createTablePage(originalContainer, thead, rows, includeCaption = false) |
|
|
|
const container = originalContainer.cloneNode(false); |
|
|
|
const table = document.createElement('table'); |
|
|
|
|
|
|
|
const originalTable = originalContainer.querySelector('table'); |
|
|
|
const originalTable = originalContainer//.querySelector('table');
|
|
|
|
table.className = originalTable.className; |
|
|
|
table.style.cssText = originalTable.style.cssText; |
|
|
|
|
|
|
|
@ -237,8 +433,7 @@ function createTablePage(originalContainer, thead, rows, includeCaption = false) |
|
|
|
rows.forEach(row => tbody.appendChild(row.cloneNode(true))); |
|
|
|
table.appendChild(tbody); |
|
|
|
|
|
|
|
container.appendChild(table); |
|
|
|
return container; |
|
|
|
return table; |
|
|
|
} |
|
|
|
|
|
|
|
function buildPrintPages(pages, structure, options) { |
|
|
|
@ -260,7 +455,9 @@ function buildPrintPages(pages, structure, options) { |
|
|
|
|
|
|
|
const contentDiv = document.createElement('div'); |
|
|
|
contentDiv.className = 'print-page-content'; |
|
|
|
|
|
|
|
contentDiv.style.display="flex" |
|
|
|
contentDiv.style.flexWrap="wrap" |
|
|
|
|
|
|
|
elements.forEach(el => { |
|
|
|
if (el?.nodeType === Node.ELEMENT_NODE) { |
|
|
|
contentDiv.appendChild(el); |
|
|
|
@ -313,7 +510,7 @@ function printViaIframe(content, options) { |
|
|
|
} |
|
|
|
.print-page { |
|
|
|
page-break-after: always; |
|
|
|
padding:${margin}mm ${margin+5}mm; |
|
|
|
padding: ${margin}mm ${margin+5}mm 0 ${margin+5}mm; |
|
|
|
} |
|
|
|
.print-page:last-child { |
|
|
|
page-break-after: avoid; |
|
|
|
|