@jcyao/print-sdk 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +190 -20
- package/dist/index.d.ts +1 -0
- package/dist/index.esm.js +196 -17
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +197 -16
- package/dist/index.js.map +1 -1
- package/dist/printEngine/renderers/PageNumberRenderer.d.ts +22 -0
- package/dist/printEngine/renderers/index.d.ts +1 -0
- package/dist/printEngine.d.ts +11 -0
- package/dist/sdk.d.ts +2 -2
- package/dist/types.d.ts +25 -0
- package/dist/utils/resourceLoader.d.ts +19 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -548,7 +548,7 @@ function buildPositionStyle(xMm, yMm, widthMm, heightMm, mmToPx = 3.78) {
|
|
|
548
548
|
styles.width = `${widthMm * mmToPx}px`;
|
|
549
549
|
}
|
|
550
550
|
if (heightMm !== undefined) {
|
|
551
|
-
styles.
|
|
551
|
+
styles.height = `${heightMm * mmToPx}px`;
|
|
552
552
|
}
|
|
553
553
|
return styles;
|
|
554
554
|
}
|
|
@@ -822,13 +822,13 @@ class ImageRenderer {
|
|
|
822
822
|
const imageSrc = value || (props === null || props === void 0 ? void 0 : props.src) || '';
|
|
823
823
|
// 容器定位样式
|
|
824
824
|
const positionStyles = buildPositionStyle(layout.xMm || 0, layout.yMm || 0, layout.widthMm || COMPONENT_DEFAULT_SIZE.IMAGE_WIDTH, layout.heightMm || COMPONENT_DEFAULT_SIZE.IMAGE_HEIGHT, context.mmToPx);
|
|
825
|
-
const containerStyles = Object.assign(Object.assign({}, positionStyles), {
|
|
825
|
+
const containerStyles = Object.assign(Object.assign({}, positionStyles), { overflow: 'hidden' });
|
|
826
826
|
const containerStyleStr = buildStyleString(containerStyles);
|
|
827
|
-
//
|
|
827
|
+
// 图片样式:严格按模板尺寸渲染,避免自适应导致排版错乱
|
|
828
828
|
const imageStyle = `
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
object-fit: ${(style === null || style === void 0 ? void 0 : style.objectFit) || '
|
|
829
|
+
width: 100%;
|
|
830
|
+
height: 100%;
|
|
831
|
+
object-fit: ${(style === null || style === void 0 ? void 0 : style.objectFit) || 'fill'};
|
|
832
832
|
`;
|
|
833
833
|
if (imageSrc) {
|
|
834
834
|
return `<div style="${containerStyleStr}"><img src="${imageSrc}" style="${imageStyle}" alt="" /></div>`;
|
|
@@ -1199,14 +1199,108 @@ class PrintEngine {
|
|
|
1199
1199
|
// 使用 MIN_ROW_HEIGHT * ROW_HEIGHT_FACTOR 作为行高
|
|
1200
1200
|
return TABLE_DEFAULT.MIN_ROW_HEIGHT * TABLE_DEFAULT.ROW_HEIGHT_FACTOR;
|
|
1201
1201
|
}
|
|
1202
|
+
/**
|
|
1203
|
+
* 渲染页码(根据页面配置)
|
|
1204
|
+
*/
|
|
1205
|
+
renderPageNumber(pageNumber, totalPages) {
|
|
1206
|
+
var _a, _b, _c, _d;
|
|
1207
|
+
const { page } = this.template;
|
|
1208
|
+
const pageNumberConfig = page.pageNumber;
|
|
1209
|
+
// 如果未启用页码或缺少页码信息,返回空
|
|
1210
|
+
if (!(pageNumberConfig === null || pageNumberConfig === void 0 ? void 0 : pageNumberConfig.enabled) || pageNumber === undefined || totalPages === undefined) {
|
|
1211
|
+
return '';
|
|
1212
|
+
}
|
|
1213
|
+
console.log(`[PrintEngine] 渲染页码: pageNumber=${pageNumber}, totalPages=${totalPages}`, pageNumberConfig);
|
|
1214
|
+
// 格式化页码文本
|
|
1215
|
+
const format = pageNumberConfig.format || 'slash';
|
|
1216
|
+
const prefix = pageNumberConfig.prefix || '';
|
|
1217
|
+
const suffix = pageNumberConfig.suffix || '';
|
|
1218
|
+
const separator = pageNumberConfig.separator || '/';
|
|
1219
|
+
let pageText = '';
|
|
1220
|
+
if (format === 'simple') {
|
|
1221
|
+
pageText = `${pageNumber}`;
|
|
1222
|
+
}
|
|
1223
|
+
else if (format === 'text') {
|
|
1224
|
+
pageText = `第${pageNumber}页 共${totalPages}页`;
|
|
1225
|
+
}
|
|
1226
|
+
else {
|
|
1227
|
+
pageText = `${pageNumber}${separator}${totalPages}`;
|
|
1228
|
+
}
|
|
1229
|
+
pageText = `${prefix}${pageText}${suffix}`;
|
|
1230
|
+
// 计算位置
|
|
1231
|
+
const { widthMm, heightMm } = this.getPageSize();
|
|
1232
|
+
const { position, offsetX = 0, offsetY = 0, style = {} } = pageNumberConfig;
|
|
1233
|
+
const fontSize = style.fontSize || 12;
|
|
1234
|
+
const color = style.color || '#666';
|
|
1235
|
+
const fontWeight = style.fontWeight || 'normal';
|
|
1236
|
+
// 根据 position 计算 x, y 坐标
|
|
1237
|
+
let xMm = 0;
|
|
1238
|
+
let yMm = 0;
|
|
1239
|
+
const pageNumberWidth = 20; // 页码宽度 mm
|
|
1240
|
+
const pageNumberHeight = 6; // 页码高度 mm
|
|
1241
|
+
const marginTop = ((_a = page.marginMm) === null || _a === void 0 ? void 0 : _a.top) || 0;
|
|
1242
|
+
const marginRight = ((_b = page.marginMm) === null || _b === void 0 ? void 0 : _b.right) || 0;
|
|
1243
|
+
const marginBottom = ((_c = page.marginMm) === null || _c === void 0 ? void 0 : _c.bottom) || 0;
|
|
1244
|
+
const marginLeft = ((_d = page.marginMm) === null || _d === void 0 ? void 0 : _d.left) || 0;
|
|
1245
|
+
switch (position) {
|
|
1246
|
+
case 'top-left':
|
|
1247
|
+
xMm = marginLeft;
|
|
1248
|
+
yMm = marginTop;
|
|
1249
|
+
break;
|
|
1250
|
+
case 'top-center':
|
|
1251
|
+
xMm = (widthMm - pageNumberWidth) / 2;
|
|
1252
|
+
yMm = marginTop;
|
|
1253
|
+
break;
|
|
1254
|
+
case 'top-right':
|
|
1255
|
+
xMm = widthMm - marginRight - pageNumberWidth;
|
|
1256
|
+
yMm = marginTop;
|
|
1257
|
+
break;
|
|
1258
|
+
case 'bottom-left':
|
|
1259
|
+
xMm = marginLeft;
|
|
1260
|
+
yMm = heightMm - marginBottom - pageNumberHeight;
|
|
1261
|
+
break;
|
|
1262
|
+
case 'bottom-center':
|
|
1263
|
+
xMm = (widthMm - pageNumberWidth) / 2;
|
|
1264
|
+
yMm = heightMm - marginBottom - pageNumberHeight;
|
|
1265
|
+
break;
|
|
1266
|
+
case 'bottom-right':
|
|
1267
|
+
default:
|
|
1268
|
+
xMm = widthMm - marginRight - pageNumberWidth;
|
|
1269
|
+
yMm = heightMm - marginBottom - pageNumberHeight;
|
|
1270
|
+
break;
|
|
1271
|
+
}
|
|
1272
|
+
// 应用偏移
|
|
1273
|
+
xMm += offsetX;
|
|
1274
|
+
yMm += offsetY;
|
|
1275
|
+
// 转换为 px
|
|
1276
|
+
const xPx = xMm * this.mmToPx;
|
|
1277
|
+
const yPx = yMm * this.mmToPx;
|
|
1278
|
+
const widthPx = pageNumberWidth * this.mmToPx;
|
|
1279
|
+
const heightPx = pageNumberHeight * this.mmToPx;
|
|
1280
|
+
// 生成 HTML
|
|
1281
|
+
const alignStyle = position.includes('left') ? 'left' : position.includes('right') ? 'right' : 'center';
|
|
1282
|
+
const justifyContent = alignStyle === 'left' ? 'flex-start' : alignStyle === 'right' ? 'flex-end' : 'center';
|
|
1283
|
+
return `<div style="position: absolute; left: ${xPx}px; top: ${yPx}px; width: ${widthPx}px; height: ${heightPx}px; font-size: ${fontSize}px; color: ${color}; font-weight: ${fontWeight}; display: flex; align-items: center; justify-content: ${justifyContent};">${this.escapeHtml(pageText)}</div>`;
|
|
1284
|
+
}
|
|
1285
|
+
/**
|
|
1286
|
+
* HTML 转义
|
|
1287
|
+
*/
|
|
1288
|
+
escapeHtml(text) {
|
|
1289
|
+
return text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''');
|
|
1290
|
+
}
|
|
1202
1291
|
/**
|
|
1203
1292
|
* 渲染单个页面(直接渲染,不做智能布局)
|
|
1293
|
+
* @param components 组件列表
|
|
1294
|
+
* @param pageNumber 当前页码(可选)
|
|
1295
|
+
* @param totalPages 总页数(可选)
|
|
1204
1296
|
*/
|
|
1205
|
-
renderSinglePage(components) {
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1297
|
+
renderSinglePage(components, pageNumber, totalPages) {
|
|
1298
|
+
console.log(`[PrintEngine] renderSinglePage: 页码=${pageNumber}, 总页数=${totalPages}, 组件数=${components.length}`);
|
|
1299
|
+
// 渲染所有组件
|
|
1300
|
+
const componentsHTML = components.map(comp => this.renderComponent(comp)).join('');
|
|
1301
|
+
// 如果页面配置启用了页码,在固定位置渲染页码
|
|
1302
|
+
const pageNumberHTML = this.renderPageNumber(pageNumber, totalPages);
|
|
1303
|
+
return componentsHTML + pageNumberHTML;
|
|
1210
1304
|
}
|
|
1211
1305
|
/**
|
|
1212
1306
|
* 虚拟分页:基于相对间距的流式布局
|
|
@@ -1439,10 +1533,12 @@ class PrintEngine {
|
|
|
1439
1533
|
}
|
|
1440
1534
|
// 标准页面模式:虚拟分页,生成多个独立的页面
|
|
1441
1535
|
const pages = this.calculatePages(components);
|
|
1536
|
+
const totalPages = pages.length;
|
|
1442
1537
|
// 渲染每个页面
|
|
1443
1538
|
const pagesHTML = pages.map((pageComponents, index) => {
|
|
1444
|
-
const
|
|
1445
|
-
|
|
1539
|
+
const pageNumber = index + 1;
|
|
1540
|
+
const pageContent = this.renderSinglePage(pageComponents, pageNumber, totalPages);
|
|
1541
|
+
return `<div class="print-page" data-page="${pageNumber}">${pageContent}</div>`;
|
|
1446
1542
|
}).join('');
|
|
1447
1543
|
const styles = generatePrintPageStyles({
|
|
1448
1544
|
pageWidthMm: widthMm,
|
|
@@ -1487,6 +1583,83 @@ function createPrintEngine(template, data) {
|
|
|
1487
1583
|
};
|
|
1488
1584
|
}
|
|
1489
1585
|
|
|
1586
|
+
/**
|
|
1587
|
+
* 资源加载工具
|
|
1588
|
+
* 用于等待图片、二维码、条形码等异步资源加载完成
|
|
1589
|
+
*/
|
|
1590
|
+
/**
|
|
1591
|
+
* 等待文档中所有图片加载完成
|
|
1592
|
+
* @param doc 文档对象(可以是 window.document 或 iframe.contentDocument)
|
|
1593
|
+
* @param timeout 超时时间(毫秒),默认 10000ms
|
|
1594
|
+
* @returns Promise<void>
|
|
1595
|
+
*/
|
|
1596
|
+
async function waitForImagesLoaded(doc, timeout = 10000) {
|
|
1597
|
+
const images = Array.from(doc.querySelectorAll('img'));
|
|
1598
|
+
if (images.length === 0) {
|
|
1599
|
+
// 没有图片,直接返回
|
|
1600
|
+
return Promise.resolve();
|
|
1601
|
+
}
|
|
1602
|
+
return new Promise((resolve, reject) => {
|
|
1603
|
+
let loadedCount = 0;
|
|
1604
|
+
let errorCount = 0;
|
|
1605
|
+
const totalImages = images.length;
|
|
1606
|
+
// 超时处理
|
|
1607
|
+
const timeoutId = setTimeout(() => {
|
|
1608
|
+
console.warn(`图片加载超时,已加载 ${loadedCount}/${totalImages},失败 ${errorCount} 张`);
|
|
1609
|
+
resolve(); // 超时也算完成,避免无限等待
|
|
1610
|
+
}, timeout);
|
|
1611
|
+
// 检查是否所有图片都已完成
|
|
1612
|
+
const checkComplete = () => {
|
|
1613
|
+
if (loadedCount + errorCount >= totalImages) {
|
|
1614
|
+
clearTimeout(timeoutId);
|
|
1615
|
+
if (errorCount > 0) {
|
|
1616
|
+
console.warn(`有 ${errorCount} 张图片加载失败`);
|
|
1617
|
+
}
|
|
1618
|
+
resolve();
|
|
1619
|
+
}
|
|
1620
|
+
};
|
|
1621
|
+
// 为每个图片添加加载监听
|
|
1622
|
+
images.forEach((img) => {
|
|
1623
|
+
// 如果图片已经加载完成(cached)
|
|
1624
|
+
if (img.complete) {
|
|
1625
|
+
if (img.naturalHeight !== 0) {
|
|
1626
|
+
loadedCount++;
|
|
1627
|
+
}
|
|
1628
|
+
else {
|
|
1629
|
+
errorCount++;
|
|
1630
|
+
}
|
|
1631
|
+
checkComplete();
|
|
1632
|
+
}
|
|
1633
|
+
else {
|
|
1634
|
+
// 监听 load 和 error 事件
|
|
1635
|
+
img.addEventListener('load', () => {
|
|
1636
|
+
loadedCount++;
|
|
1637
|
+
checkComplete();
|
|
1638
|
+
});
|
|
1639
|
+
img.addEventListener('error', (e) => {
|
|
1640
|
+
errorCount++;
|
|
1641
|
+
console.error('图片加载失败:', img.src, e);
|
|
1642
|
+
checkComplete();
|
|
1643
|
+
});
|
|
1644
|
+
}
|
|
1645
|
+
});
|
|
1646
|
+
// 立即检查一次(可能所有图片都已缓存)
|
|
1647
|
+
checkComplete();
|
|
1648
|
+
});
|
|
1649
|
+
}
|
|
1650
|
+
/**
|
|
1651
|
+
* 等待所有打印资源加载完成
|
|
1652
|
+
* 包括:图片、二维码(已转base64)、条形码(已转base64)
|
|
1653
|
+
* 注意:二维码和条形码在渲染时已同步生成为 base64,所以主要等待外部图片
|
|
1654
|
+
* @param doc 文档对象
|
|
1655
|
+
* @param timeout 超时时间(毫秒),默认 10000ms
|
|
1656
|
+
*/
|
|
1657
|
+
async function waitForPrintResourcesReady(doc, timeout = 10000) {
|
|
1658
|
+
// 目前只需要等待图片加载
|
|
1659
|
+
// 二维码和条形码在 QRCodeRenderer 和 BarcodeRenderer 中已同步生成为 base64
|
|
1660
|
+
return waitForImagesLoaded(doc, timeout);
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1490
1663
|
/**
|
|
1491
1664
|
* 打印 SDK 核心类
|
|
1492
1665
|
* 提供完整的打印功能封装
|
|
@@ -1514,6 +1687,8 @@ class PrintSDK {
|
|
|
1514
1687
|
const html = engine.generatePrintHTML();
|
|
1515
1688
|
printWindow.document.write(html);
|
|
1516
1689
|
printWindow.document.close();
|
|
1690
|
+
// 等待所有图片加载完成后再打印
|
|
1691
|
+
await waitForImagesLoaded(printWindow.document);
|
|
1517
1692
|
printWindow.print();
|
|
1518
1693
|
}
|
|
1519
1694
|
else {
|
|
@@ -1530,7 +1705,9 @@ class PrintSDK {
|
|
|
1530
1705
|
}
|
|
1531
1706
|
iframeDoc.write(html);
|
|
1532
1707
|
iframeDoc.close();
|
|
1533
|
-
//
|
|
1708
|
+
// 等待所有图片加载完成后再打印
|
|
1709
|
+
// 注意:二维码和条形码已同步生成为base64,主要等待外部图片资源
|
|
1710
|
+
await waitForImagesLoaded(iframeDoc);
|
|
1534
1711
|
(_b = iframe.contentWindow) === null || _b === void 0 ? void 0 : _b.print();
|
|
1535
1712
|
// 打印完成后移除 iframe
|
|
1536
1713
|
setTimeout(() => {
|
|
@@ -1633,7 +1810,8 @@ class PrintSDK {
|
|
|
1633
1810
|
}
|
|
1634
1811
|
printWindow.document.write(fullHTML);
|
|
1635
1812
|
printWindow.document.close();
|
|
1636
|
-
//
|
|
1813
|
+
// 等待所有图片加载完成后再打印
|
|
1814
|
+
await waitForImagesLoaded(printWindow.document);
|
|
1637
1815
|
printWindow.print();
|
|
1638
1816
|
}
|
|
1639
1817
|
else {
|
|
@@ -1649,7 +1827,8 @@ class PrintSDK {
|
|
|
1649
1827
|
}
|
|
1650
1828
|
iframeDoc.write(fullHTML);
|
|
1651
1829
|
iframeDoc.close();
|
|
1652
|
-
//
|
|
1830
|
+
// 等待所有图片加载完成后再打印
|
|
1831
|
+
await waitForImagesLoaded(iframeDoc);
|
|
1653
1832
|
(_b = iframe.contentWindow) === null || _b === void 0 ? void 0 : _b.print();
|
|
1654
1833
|
// 打印完成后移除 iframe
|
|
1655
1834
|
setTimeout(() => {
|
|
@@ -1699,4 +1878,6 @@ exports.getAllPipes = getAllPipes;
|
|
|
1699
1878
|
exports.getExecutor = getExecutor;
|
|
1700
1879
|
exports.getPageSizeFromConfig = getPageSizeFromConfig;
|
|
1701
1880
|
exports.registerExecutor = registerExecutor;
|
|
1881
|
+
exports.waitForImagesLoaded = waitForImagesLoaded;
|
|
1882
|
+
exports.waitForPrintResourcesReady = waitForPrintResourcesReady;
|
|
1702
1883
|
//# sourceMappingURL=index.js.map
|