@worm-vue3-print/core 1.0.1 → 1.1.0

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/dist/index.cjs CHANGED
@@ -931,6 +931,9 @@ function makeRenderRow(row, resolve, keepRaw = false) {
931
931
  barcodeType: cell.barcodeType,
932
932
  qrCodeLevel: cell.qrCodeLevel,
933
933
  showBarcodeText: cell.showBarcodeText,
934
+ fit: cell.fit,
935
+ maxWidth: cell.maxWidth,
936
+ maxHeight: cell.maxHeight,
934
937
  rowspan: cell.rowspan ?? 1,
935
938
  colspan: cell.colspan ?? 1,
936
939
  merged: cell.merged === true,
@@ -997,6 +1000,10 @@ function buildPageCss(template) {
997
1000
 
998
1001
  /* \u2500\u2500 \u5168\u5C40\u91CD\u7F6E \u2500\u2500 */
999
1002
  * { margin: 0; padding: 0; box-sizing: border-box; }
1003
+
1004
+ /* \u6253\u5370\u5FC5\u987B\u4FDD\u7559\u5143\u7D20\u80CC\u666F\u8272\uFF08Chromium \u9ED8\u8BA4\u5254\u9664\u80CC\u666F\uFF0C\u9700\u663E\u5F0F\u58F0\u660E\uFF09 */
1005
+ * { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
1006
+
1000
1007
  body { font-family: "Microsoft YaHei", "PingFang SC", "Helvetica Neue", Arial, sans-serif; }
1001
1008
 
1002
1009
  /* \u2500\u2500 \u5C4F\u5E55\u9884\u89C8\uFF1A\u7070\u5E95 + \u7EB8\u5F20\u9634\u5F71/\u9875\u95F4\u8DDD\uFF1B\u6253\u5370\u65F6\u53BB\u9664 \u2500\u2500 */
@@ -1013,6 +1020,7 @@ body { font-family: "Microsoft YaHei", "PingFang SC", "Helvetica Neue", Arial, s
1013
1020
  .print-page {
1014
1021
  width: ${mm(paper.width)};
1015
1022
  min-height: ${mm(paper.height)};
1023
+ background: ${template.pageBackground ?? "#fff"};
1016
1024
  padding: ${mm(mt)} ${mm(mr)} ${mm(mb)} ${mm(ml)};
1017
1025
  position: relative;
1018
1026
  page-break-after: always;
@@ -1029,12 +1037,14 @@ body { font-family: "Microsoft YaHei", "PingFang SC", "Helvetica Neue", Arial, s
1029
1037
  position: relative;
1030
1038
  }
1031
1039
 
1032
- /* \u2500\u2500 \u9875\u811A\uFF1A\u7EDD\u5BF9\u5B9A\u4F4D\u56FA\u5B9A\u5728\u9875\u9762\u5E95\u90E8\uFF08\u5185\u5BB9\u4E0D\u8DB3\u65F6\u4E0D\u968F\u6587\u6863\u6D41\u4E0A\u6D6E\uFF09 \u2500\u2500 */
1040
+ /* \u2500\u2500 \u9875\u811A\uFF1A\u7EDD\u5BF9\u5B9A\u4F4D\u56FA\u5B9A\u5728\u9875\u9762\u5E95\u90E8\uFF08\u5185\u5BB9\u4E0D\u8DB3\u65F6\u4E0D\u968F\u6587\u6863\u6D41\u4E0A\u6D6E\uFF09 \u2500\u2500
1041
+ \u7528\u663E\u5F0F top \u5B9A\u4F4D\u5230\u300C\u7EB8\u9AD8 - \u4E0B\u8FB9\u8DDD - \u9875\u811A\u9AD8\u300D\uFF0C\u4FDD\u8BC1\u4E0B\u8FB9\u8DDD\u751F\u6548\u3001
1042
+ \u4E0E\u8BBE\u8BA1\u5668 CanvasPaper \u7684\u4E09\u533A\u51E0\u4F55\u4E00\u81F4\u3002 */
1033
1043
  .page-footer {
1034
1044
  width: ${mm(contentWidth)};
1035
1045
  height: ${mm(footerH)};
1036
1046
  position: absolute;
1037
- bottom: 0;
1047
+ top: ${mm(paper.height - mb - footerH)};
1038
1048
  left: 0;
1039
1049
  }
1040
1050
 
@@ -1088,7 +1098,7 @@ body { font-family: "Microsoft YaHei", "PingFang SC", "Helvetica Neue", Arial, s
1088
1098
  }
1089
1099
  `.trim();
1090
1100
  }
1091
- function elementPositionStyle(left, top, width, height) {
1101
+ function elementPositionStyle(left, top, width, height, zIndex) {
1092
1102
  const parts = [
1093
1103
  `position:absolute`,
1094
1104
  `left:${mm(left)}`,
@@ -1098,6 +1108,9 @@ function elementPositionStyle(left, top, width, height) {
1098
1108
  if (height !== void 0 && height > 0) {
1099
1109
  parts.push(`height:${mm(height)}`);
1100
1110
  }
1111
+ if (zIndex !== void 0) {
1112
+ parts.push(`z-index:${zIndex}`);
1113
+ }
1101
1114
  return parts.join(";") + ";";
1102
1115
  }
1103
1116
 
@@ -1139,18 +1152,15 @@ function buildFollowMap(sorted) {
1139
1152
  }
1140
1153
  return map;
1141
1154
  }
1142
- function followGroupHeight(el, members, template, measuredElements) {
1143
- let cursorBottom = tableDesignBottom(el);
1144
- let total = 0;
1155
+ function followGroupHeight(el, members, template, _measuredElements) {
1156
+ const tableBottom = tableDesignBottom(el);
1157
+ let maxBottom = tableBottom;
1145
1158
  for (const id of members) {
1146
1159
  const m = template.elements.find((e) => e.id === id);
1147
1160
  if (!m) continue;
1148
- const mTop = m.options?.top ?? 0;
1149
- total += Math.max(mTop - cursorBottom, 0);
1150
- total += measuredElements.get(id)?.measuredHeight ?? m.options?.height ?? 0;
1151
- cursorBottom = mTop + (m.options?.height ?? 0);
1161
+ maxBottom = Math.max(maxBottom, (m.options?.top ?? 0) + (m.options?.height ?? 0));
1152
1162
  }
1153
- return total;
1163
+ return Math.max(maxBottom - tableBottom, 0);
1154
1164
  }
1155
1165
  function paginate(template, measuredElements) {
1156
1166
  const paper = getPaperDimensions(template);
@@ -1536,7 +1546,7 @@ function renderElement(el, isMeasure, containerStyle, overrideTop, ctx) {
1536
1546
  const top = overrideTop ?? opts.top ?? 0;
1537
1547
  const width = opts.width ?? 100;
1538
1548
  const height = opts.height ?? void 0;
1539
- const style = containerStyle ?? elementPositionStyle(left, top, width, height);
1549
+ const style = containerStyle ?? elementPositionStyle(left, top, width, height, opts.zIndex);
1540
1550
  const measureAttr = isMeasure ? ` data-measure-id="${el.id}"` : "";
1541
1551
  const type = el.type || el.printElementType?.type || "text";
1542
1552
  switch (type) {
@@ -1570,7 +1580,7 @@ function renderElement(el, isMeasure, containerStyle, overrideTop, ctx) {
1570
1580
  return `<div class="print-element" style="${style}${textStyle(opts)}"${measureAttr}>${esc(opts.formatter ?? opts.testData ?? "")}</div>`;
1571
1581
  }
1572
1582
  }
1573
- function codeImgHtml(value, cellType, opts, fallbackHtml, fill = false, codeRenderer) {
1583
+ function codeImgHtml(value, cellType, opts, fallbackHtml, fill = false, codeRenderer, fit, maxWidth, maxHeight) {
1574
1584
  if (!value || !codeRenderer) return fallbackHtml;
1575
1585
  try {
1576
1586
  const svg = codeRenderer.render(value, cellType, {
@@ -1581,7 +1591,22 @@ function codeImgHtml(value, cellType, opts, fallbackHtml, fill = false, codeRend
1581
1591
  fontSize: typeof opts.fontSize === "number" ? opts.fontSize : void 0
1582
1592
  });
1583
1593
  const src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;
1584
- const style = fill ? "width:100%;height:100%;object-fit:contain;display:block;margin:auto;" : "max-width:100%;max-height:100%;display:block;margin:auto;";
1594
+ const styleParts = [];
1595
+ if (fill) {
1596
+ styleParts.push("width:100%", "height:100%", "object-fit:contain", "display:block", "margin:auto");
1597
+ } else {
1598
+ styleParts.push("max-width:100%", "max-height:100%", "display:block", "margin:auto");
1599
+ }
1600
+ if (fit) {
1601
+ styleParts.push(`object-fit:${fit}`);
1602
+ }
1603
+ if (maxWidth) {
1604
+ styleParts.push(`max-width:${maxWidth}mm`);
1605
+ }
1606
+ if (maxHeight) {
1607
+ styleParts.push(`max-height:${maxHeight}mm`);
1608
+ }
1609
+ const style = styleParts.join(";");
1585
1610
  return `<img src="${src}" style="${style}" />`;
1586
1611
  } catch {
1587
1612
  return fallbackHtml;
@@ -1618,7 +1643,12 @@ function renderMatrixRows(renderRows, start, end, opts, withRowIndex, ctx) {
1618
1643
  let inner;
1619
1644
  if (cell.cellType === "barcode" || cell.cellType === "qrcode") {
1620
1645
  const cellFill = cell.cellType === "barcode";
1621
- inner = `<div style="width:100%;height:100%;display:flex;align-items:center;justify-content:center;overflow:hidden;">${codeImgHtml(cell.content, cell.cellType, cell, esc(cell.content), cellFill, ctx?.codeRenderer)}</div>`;
1646
+ inner = `<div style="width:100%;height:100%;display:flex;align-items:center;justify-content:center;overflow:hidden;">${codeImgHtml(cell.content, cell.cellType, cell, esc(cell.content), cellFill, ctx?.codeRenderer, cell.fit, cell.maxWidth, cell.maxHeight)}</div>`;
1647
+ } else if (cell.cellType === "image") {
1648
+ const fit = cell.fit || "contain";
1649
+ const maxWidth = cell.maxWidth ? `max-width:${cell.maxWidth}mm;` : "max-width:100%;";
1650
+ const maxHeight = cell.maxHeight ? `max-height:${cell.maxHeight}mm;` : "max-height:100%;";
1651
+ inner = `<div style="width:100%;height:100%;display:flex;align-items:center;justify-content:center;overflow:hidden;"><img src="${esc(cell.content)}" style="object-fit:${fit};${maxWidth}${maxHeight}display:block;" /></div>`;
1622
1652
  } else {
1623
1653
  inner = esc(cell.content);
1624
1654
  }
@@ -1640,7 +1670,7 @@ function matrixTableHtml(el, bodyHtml) {
1640
1670
  }
1641
1671
  function renderTableElement(el, isMeasure, measureAttr, ctx) {
1642
1672
  const opts = el.options;
1643
- const style = elementPositionStyle(opts.left ?? 0, opts.top ?? 0, opts.width ?? 100);
1673
+ const style = elementPositionStyle(opts.left ?? 0, opts.top ?? 0, opts.width ?? 100, void 0, opts.zIndex);
1644
1674
  const renderRows = opts._renderRows ?? [];
1645
1675
  const bodyHtml = renderMatrixRows(renderRows, 0, renderRows.length, opts, isMeasure, ctx);
1646
1676
  return `<div class="print-element" style="${style};overflow:visible;"${measureAttr}>
@@ -1649,7 +1679,7 @@ function renderTableElement(el, isMeasure, measureAttr, ctx) {
1649
1679
  }
1650
1680
  function renderTableSlice(el, section, ctx) {
1651
1681
  const opts = el.options;
1652
- const style = elementPositionStyle(opts.left ?? 0, section.renderTop ?? opts.top ?? 0, opts.width ?? 100);
1682
+ const style = elementPositionStyle(opts.left ?? 0, section.renderTop ?? opts.top ?? 0, opts.width ?? 100, void 0, opts.zIndex);
1653
1683
  const renderRows = opts._renderRows ?? [];
1654
1684
  const startRow = section.startRow ?? 0;
1655
1685
  const endRow = section.endRow ?? renderRows.length;
@@ -1691,7 +1721,9 @@ function renderFlowGroup(el, section, template, ctx) {
1691
1721
  const tableLeft = opts.left ?? 0;
1692
1722
  const tableWidth = opts.width ?? 100;
1693
1723
  const groupTop = section.groupTop ?? opts.top ?? 0;
1694
- const style = `position:absolute;left:${mm(tableLeft)};top:${mm(groupTop)};width:${mm(tableWidth)};overflow:visible;`;
1724
+ const tableBottom = tableDesignBottom(el);
1725
+ const zStyle = typeof opts.zIndex === "number" ? `z-index:${opts.zIndex};` : "";
1726
+ const style = `position:absolute;left:${mm(tableLeft)};top:${mm(groupTop)};width:${mm(tableWidth)};overflow:visible;${zStyle}`;
1695
1727
  const startRow = section.startRow ?? 0;
1696
1728
  const endRow = section.endRow ?? 0;
1697
1729
  let sliceHtml = "";
@@ -1707,31 +1739,39 @@ function renderFlowGroup(el, section, template, ctx) {
1707
1739
  ${bodyHtml}${subtotalHtml}${summaryHtml}`)}
1708
1740
  </div>`;
1709
1741
  }
1710
- let cursorBottom = tableDesignBottom(el);
1711
- const followHtml = (section.followElementIds ?? []).map((id) => {
1742
+ const followIds = section.followElementIds ?? [];
1743
+ const followExtent = followIds.reduce((maxBottom, id) => {
1744
+ const m = findElement(template, id);
1745
+ if (!m) return maxBottom;
1746
+ return Math.max(maxBottom, (m.options?.top ?? 0) + (m.options?.height ?? 0));
1747
+ }, tableBottom);
1748
+ const followHtml = followIds.map((id) => {
1712
1749
  const m = findElement(template, id);
1713
1750
  if (!m) return "";
1714
1751
  const mTop = m.options?.top ?? 0;
1715
1752
  const mLeft = m.options?.left ?? 0;
1716
1753
  const mWidth = m.options?.width ?? tableWidth;
1717
1754
  const mHeight = m.options?.height ?? 0;
1718
- const gap = Math.max(mTop - cursorBottom, 0);
1719
- cursorBottom = mTop + mHeight;
1755
+ const mZ = m.options?.zIndex;
1720
1756
  const type = m.type || m.printElementType?.type || "text";
1721
1757
  const needsHeight = type === "rect" || type === "oval" || type === "image";
1722
1758
  const flowStyle = [
1723
- "position:relative",
1759
+ "position:absolute",
1724
1760
  `left:${mm(mLeft - tableLeft)}`,
1761
+ `top:${mm(mTop - tableBottom)}`,
1725
1762
  `width:${mm(mWidth)}`,
1726
1763
  ...needsHeight && mHeight > 0 ? [`height:${mm(mHeight)}`] : [],
1727
- `margin-top:${mm(gap)}`,
1764
+ ...typeof mZ === "number" ? [`z-index:${mZ}`] : [],
1728
1765
  "overflow:visible"
1729
1766
  ].join(";") + ";";
1730
1767
  return renderElement(m, false, flowStyle, void 0, ctx);
1731
1768
  }).join("\n");
1769
+ const followWrap = followIds.length > 0 ? `<div class="flow-follow" style="position:relative;width:${mm(tableWidth)};height:${mm(Math.max(followExtent - tableBottom, 0))};overflow:visible;">
1770
+ ${followHtml}
1771
+ </div>` : "";
1732
1772
  return `<div class="flow-group" style="${style}">
1733
1773
  ${sliceHtml}
1734
- ${followHtml}
1774
+ ${followWrap}
1735
1775
  </div>`;
1736
1776
  }
1737
1777
  function renderAreaElements(elements, _contentWidth, pageIndex, totalPages, ctx) {
package/dist/index.d.cts CHANGED
@@ -139,6 +139,8 @@ interface TemplateData {
139
139
  customWidth?: number;
140
140
  /** 自定义纸张高度(mm),仅 paperSize='CUSTOM' 时生效 */
141
141
  customHeight?: number;
142
+ /** 页面(纸张)背景色;未设置时默认白色 */
143
+ pageBackground?: string;
142
144
  margins: {
143
145
  top: number;
144
146
  right: number;
@@ -206,7 +208,7 @@ interface RenderCell {
206
208
  content: string;
207
209
  /** 小计行专用:单元格原始 formatter 表达式,渲染阶段以当前页数据行上下文求值 */
208
210
  rawFormatter?: string;
209
- /** 内容类型:text(默认)/ barcode / qrcode,非 text 时 content 为码值 */
211
+ /** 内容类型:text(默认)/ barcode / qrcode / image,非 text 时 content 为码值或图片 URL */
210
212
  cellType?: string;
211
213
  /** 条形码码制(jsbarcode 格式名),仅 cellType='barcode' 时生效 */
212
214
  barcodeType?: string;
@@ -214,6 +216,12 @@ interface RenderCell {
214
216
  qrCodeLevel?: string;
215
217
  /** 条形码下方是否显示文本,仅 cellType='barcode' 时生效 */
216
218
  showBarcodeText?: boolean;
219
+ /** 图片缩放模式,仅 cellType='image' 时生效 */
220
+ fit?: string;
221
+ /** 图片最大宽度(mm),仅 cellType='image' 时生效 */
222
+ maxWidth?: number;
223
+ /** 图片最大高度(mm),仅 cellType='image' 时生效 */
224
+ maxHeight?: number;
217
225
  rowspan: number;
218
226
  colspan: number;
219
227
  merged: boolean;
@@ -330,9 +338,10 @@ declare function mm(value: number): string;
330
338
  */
331
339
  declare function buildPageCss(template: TemplateData): string;
332
340
  /**
333
- * 生成内联样式:元素绝对定位(mm 单位)
341
+ * 生成内联样式:元素绝对定位(mm 单位)。
342
+ * 可选 z-index:与设计器「层级」一致,未设置(undefined)时不输出(层叠回退 DOM 顺序)。
334
343
  */
335
- declare function elementPositionStyle(left: number, top: number, width: number, height?: number): string;
344
+ declare function elementPositionStyle(left: number, top: number, width: number, height?: number, zIndex?: number): string;
336
345
 
337
346
  /**
338
347
  * 表格设计底部(mm)= 设计 top + 设计高度。
package/dist/index.d.ts CHANGED
@@ -139,6 +139,8 @@ interface TemplateData {
139
139
  customWidth?: number;
140
140
  /** 自定义纸张高度(mm),仅 paperSize='CUSTOM' 时生效 */
141
141
  customHeight?: number;
142
+ /** 页面(纸张)背景色;未设置时默认白色 */
143
+ pageBackground?: string;
142
144
  margins: {
143
145
  top: number;
144
146
  right: number;
@@ -206,7 +208,7 @@ interface RenderCell {
206
208
  content: string;
207
209
  /** 小计行专用:单元格原始 formatter 表达式,渲染阶段以当前页数据行上下文求值 */
208
210
  rawFormatter?: string;
209
- /** 内容类型:text(默认)/ barcode / qrcode,非 text 时 content 为码值 */
211
+ /** 内容类型:text(默认)/ barcode / qrcode / image,非 text 时 content 为码值或图片 URL */
210
212
  cellType?: string;
211
213
  /** 条形码码制(jsbarcode 格式名),仅 cellType='barcode' 时生效 */
212
214
  barcodeType?: string;
@@ -214,6 +216,12 @@ interface RenderCell {
214
216
  qrCodeLevel?: string;
215
217
  /** 条形码下方是否显示文本,仅 cellType='barcode' 时生效 */
216
218
  showBarcodeText?: boolean;
219
+ /** 图片缩放模式,仅 cellType='image' 时生效 */
220
+ fit?: string;
221
+ /** 图片最大宽度(mm),仅 cellType='image' 时生效 */
222
+ maxWidth?: number;
223
+ /** 图片最大高度(mm),仅 cellType='image' 时生效 */
224
+ maxHeight?: number;
217
225
  rowspan: number;
218
226
  colspan: number;
219
227
  merged: boolean;
@@ -330,9 +338,10 @@ declare function mm(value: number): string;
330
338
  */
331
339
  declare function buildPageCss(template: TemplateData): string;
332
340
  /**
333
- * 生成内联样式:元素绝对定位(mm 单位)
341
+ * 生成内联样式:元素绝对定位(mm 单位)。
342
+ * 可选 z-index:与设计器「层级」一致,未设置(undefined)时不输出(层叠回退 DOM 顺序)。
334
343
  */
335
- declare function elementPositionStyle(left: number, top: number, width: number, height?: number): string;
344
+ declare function elementPositionStyle(left: number, top: number, width: number, height?: number, zIndex?: number): string;
336
345
 
337
346
  /**
338
347
  * 表格设计底部(mm)= 设计 top + 设计高度。
package/dist/index.js CHANGED
@@ -874,6 +874,9 @@ function makeRenderRow(row, resolve, keepRaw = false) {
874
874
  barcodeType: cell.barcodeType,
875
875
  qrCodeLevel: cell.qrCodeLevel,
876
876
  showBarcodeText: cell.showBarcodeText,
877
+ fit: cell.fit,
878
+ maxWidth: cell.maxWidth,
879
+ maxHeight: cell.maxHeight,
877
880
  rowspan: cell.rowspan ?? 1,
878
881
  colspan: cell.colspan ?? 1,
879
882
  merged: cell.merged === true,
@@ -940,6 +943,10 @@ function buildPageCss(template) {
940
943
 
941
944
  /* \u2500\u2500 \u5168\u5C40\u91CD\u7F6E \u2500\u2500 */
942
945
  * { margin: 0; padding: 0; box-sizing: border-box; }
946
+
947
+ /* \u6253\u5370\u5FC5\u987B\u4FDD\u7559\u5143\u7D20\u80CC\u666F\u8272\uFF08Chromium \u9ED8\u8BA4\u5254\u9664\u80CC\u666F\uFF0C\u9700\u663E\u5F0F\u58F0\u660E\uFF09 */
948
+ * { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
949
+
943
950
  body { font-family: "Microsoft YaHei", "PingFang SC", "Helvetica Neue", Arial, sans-serif; }
944
951
 
945
952
  /* \u2500\u2500 \u5C4F\u5E55\u9884\u89C8\uFF1A\u7070\u5E95 + \u7EB8\u5F20\u9634\u5F71/\u9875\u95F4\u8DDD\uFF1B\u6253\u5370\u65F6\u53BB\u9664 \u2500\u2500 */
@@ -956,6 +963,7 @@ body { font-family: "Microsoft YaHei", "PingFang SC", "Helvetica Neue", Arial, s
956
963
  .print-page {
957
964
  width: ${mm(paper.width)};
958
965
  min-height: ${mm(paper.height)};
966
+ background: ${template.pageBackground ?? "#fff"};
959
967
  padding: ${mm(mt)} ${mm(mr)} ${mm(mb)} ${mm(ml)};
960
968
  position: relative;
961
969
  page-break-after: always;
@@ -972,12 +980,14 @@ body { font-family: "Microsoft YaHei", "PingFang SC", "Helvetica Neue", Arial, s
972
980
  position: relative;
973
981
  }
974
982
 
975
- /* \u2500\u2500 \u9875\u811A\uFF1A\u7EDD\u5BF9\u5B9A\u4F4D\u56FA\u5B9A\u5728\u9875\u9762\u5E95\u90E8\uFF08\u5185\u5BB9\u4E0D\u8DB3\u65F6\u4E0D\u968F\u6587\u6863\u6D41\u4E0A\u6D6E\uFF09 \u2500\u2500 */
983
+ /* \u2500\u2500 \u9875\u811A\uFF1A\u7EDD\u5BF9\u5B9A\u4F4D\u56FA\u5B9A\u5728\u9875\u9762\u5E95\u90E8\uFF08\u5185\u5BB9\u4E0D\u8DB3\u65F6\u4E0D\u968F\u6587\u6863\u6D41\u4E0A\u6D6E\uFF09 \u2500\u2500
984
+ \u7528\u663E\u5F0F top \u5B9A\u4F4D\u5230\u300C\u7EB8\u9AD8 - \u4E0B\u8FB9\u8DDD - \u9875\u811A\u9AD8\u300D\uFF0C\u4FDD\u8BC1\u4E0B\u8FB9\u8DDD\u751F\u6548\u3001
985
+ \u4E0E\u8BBE\u8BA1\u5668 CanvasPaper \u7684\u4E09\u533A\u51E0\u4F55\u4E00\u81F4\u3002 */
976
986
  .page-footer {
977
987
  width: ${mm(contentWidth)};
978
988
  height: ${mm(footerH)};
979
989
  position: absolute;
980
- bottom: 0;
990
+ top: ${mm(paper.height - mb - footerH)};
981
991
  left: 0;
982
992
  }
983
993
 
@@ -1031,7 +1041,7 @@ body { font-family: "Microsoft YaHei", "PingFang SC", "Helvetica Neue", Arial, s
1031
1041
  }
1032
1042
  `.trim();
1033
1043
  }
1034
- function elementPositionStyle(left, top, width, height) {
1044
+ function elementPositionStyle(left, top, width, height, zIndex) {
1035
1045
  const parts = [
1036
1046
  `position:absolute`,
1037
1047
  `left:${mm(left)}`,
@@ -1041,6 +1051,9 @@ function elementPositionStyle(left, top, width, height) {
1041
1051
  if (height !== void 0 && height > 0) {
1042
1052
  parts.push(`height:${mm(height)}`);
1043
1053
  }
1054
+ if (zIndex !== void 0) {
1055
+ parts.push(`z-index:${zIndex}`);
1056
+ }
1044
1057
  return parts.join(";") + ";";
1045
1058
  }
1046
1059
 
@@ -1082,18 +1095,15 @@ function buildFollowMap(sorted) {
1082
1095
  }
1083
1096
  return map;
1084
1097
  }
1085
- function followGroupHeight(el, members, template, measuredElements) {
1086
- let cursorBottom = tableDesignBottom(el);
1087
- let total = 0;
1098
+ function followGroupHeight(el, members, template, _measuredElements) {
1099
+ const tableBottom = tableDesignBottom(el);
1100
+ let maxBottom = tableBottom;
1088
1101
  for (const id of members) {
1089
1102
  const m = template.elements.find((e) => e.id === id);
1090
1103
  if (!m) continue;
1091
- const mTop = m.options?.top ?? 0;
1092
- total += Math.max(mTop - cursorBottom, 0);
1093
- total += measuredElements.get(id)?.measuredHeight ?? m.options?.height ?? 0;
1094
- cursorBottom = mTop + (m.options?.height ?? 0);
1104
+ maxBottom = Math.max(maxBottom, (m.options?.top ?? 0) + (m.options?.height ?? 0));
1095
1105
  }
1096
- return total;
1106
+ return Math.max(maxBottom - tableBottom, 0);
1097
1107
  }
1098
1108
  function paginate(template, measuredElements) {
1099
1109
  const paper = getPaperDimensions(template);
@@ -1479,7 +1489,7 @@ function renderElement(el, isMeasure, containerStyle, overrideTop, ctx) {
1479
1489
  const top = overrideTop ?? opts.top ?? 0;
1480
1490
  const width = opts.width ?? 100;
1481
1491
  const height = opts.height ?? void 0;
1482
- const style = containerStyle ?? elementPositionStyle(left, top, width, height);
1492
+ const style = containerStyle ?? elementPositionStyle(left, top, width, height, opts.zIndex);
1483
1493
  const measureAttr = isMeasure ? ` data-measure-id="${el.id}"` : "";
1484
1494
  const type = el.type || el.printElementType?.type || "text";
1485
1495
  switch (type) {
@@ -1513,7 +1523,7 @@ function renderElement(el, isMeasure, containerStyle, overrideTop, ctx) {
1513
1523
  return `<div class="print-element" style="${style}${textStyle(opts)}"${measureAttr}>${esc(opts.formatter ?? opts.testData ?? "")}</div>`;
1514
1524
  }
1515
1525
  }
1516
- function codeImgHtml(value, cellType, opts, fallbackHtml, fill = false, codeRenderer) {
1526
+ function codeImgHtml(value, cellType, opts, fallbackHtml, fill = false, codeRenderer, fit, maxWidth, maxHeight) {
1517
1527
  if (!value || !codeRenderer) return fallbackHtml;
1518
1528
  try {
1519
1529
  const svg = codeRenderer.render(value, cellType, {
@@ -1524,7 +1534,22 @@ function codeImgHtml(value, cellType, opts, fallbackHtml, fill = false, codeRend
1524
1534
  fontSize: typeof opts.fontSize === "number" ? opts.fontSize : void 0
1525
1535
  });
1526
1536
  const src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;
1527
- const style = fill ? "width:100%;height:100%;object-fit:contain;display:block;margin:auto;" : "max-width:100%;max-height:100%;display:block;margin:auto;";
1537
+ const styleParts = [];
1538
+ if (fill) {
1539
+ styleParts.push("width:100%", "height:100%", "object-fit:contain", "display:block", "margin:auto");
1540
+ } else {
1541
+ styleParts.push("max-width:100%", "max-height:100%", "display:block", "margin:auto");
1542
+ }
1543
+ if (fit) {
1544
+ styleParts.push(`object-fit:${fit}`);
1545
+ }
1546
+ if (maxWidth) {
1547
+ styleParts.push(`max-width:${maxWidth}mm`);
1548
+ }
1549
+ if (maxHeight) {
1550
+ styleParts.push(`max-height:${maxHeight}mm`);
1551
+ }
1552
+ const style = styleParts.join(";");
1528
1553
  return `<img src="${src}" style="${style}" />`;
1529
1554
  } catch {
1530
1555
  return fallbackHtml;
@@ -1561,7 +1586,12 @@ function renderMatrixRows(renderRows, start, end, opts, withRowIndex, ctx) {
1561
1586
  let inner;
1562
1587
  if (cell.cellType === "barcode" || cell.cellType === "qrcode") {
1563
1588
  const cellFill = cell.cellType === "barcode";
1564
- inner = `<div style="width:100%;height:100%;display:flex;align-items:center;justify-content:center;overflow:hidden;">${codeImgHtml(cell.content, cell.cellType, cell, esc(cell.content), cellFill, ctx?.codeRenderer)}</div>`;
1589
+ inner = `<div style="width:100%;height:100%;display:flex;align-items:center;justify-content:center;overflow:hidden;">${codeImgHtml(cell.content, cell.cellType, cell, esc(cell.content), cellFill, ctx?.codeRenderer, cell.fit, cell.maxWidth, cell.maxHeight)}</div>`;
1590
+ } else if (cell.cellType === "image") {
1591
+ const fit = cell.fit || "contain";
1592
+ const maxWidth = cell.maxWidth ? `max-width:${cell.maxWidth}mm;` : "max-width:100%;";
1593
+ const maxHeight = cell.maxHeight ? `max-height:${cell.maxHeight}mm;` : "max-height:100%;";
1594
+ inner = `<div style="width:100%;height:100%;display:flex;align-items:center;justify-content:center;overflow:hidden;"><img src="${esc(cell.content)}" style="object-fit:${fit};${maxWidth}${maxHeight}display:block;" /></div>`;
1565
1595
  } else {
1566
1596
  inner = esc(cell.content);
1567
1597
  }
@@ -1583,7 +1613,7 @@ function matrixTableHtml(el, bodyHtml) {
1583
1613
  }
1584
1614
  function renderTableElement(el, isMeasure, measureAttr, ctx) {
1585
1615
  const opts = el.options;
1586
- const style = elementPositionStyle(opts.left ?? 0, opts.top ?? 0, opts.width ?? 100);
1616
+ const style = elementPositionStyle(opts.left ?? 0, opts.top ?? 0, opts.width ?? 100, void 0, opts.zIndex);
1587
1617
  const renderRows = opts._renderRows ?? [];
1588
1618
  const bodyHtml = renderMatrixRows(renderRows, 0, renderRows.length, opts, isMeasure, ctx);
1589
1619
  return `<div class="print-element" style="${style};overflow:visible;"${measureAttr}>
@@ -1592,7 +1622,7 @@ function renderTableElement(el, isMeasure, measureAttr, ctx) {
1592
1622
  }
1593
1623
  function renderTableSlice(el, section, ctx) {
1594
1624
  const opts = el.options;
1595
- const style = elementPositionStyle(opts.left ?? 0, section.renderTop ?? opts.top ?? 0, opts.width ?? 100);
1625
+ const style = elementPositionStyle(opts.left ?? 0, section.renderTop ?? opts.top ?? 0, opts.width ?? 100, void 0, opts.zIndex);
1596
1626
  const renderRows = opts._renderRows ?? [];
1597
1627
  const startRow = section.startRow ?? 0;
1598
1628
  const endRow = section.endRow ?? renderRows.length;
@@ -1634,7 +1664,9 @@ function renderFlowGroup(el, section, template, ctx) {
1634
1664
  const tableLeft = opts.left ?? 0;
1635
1665
  const tableWidth = opts.width ?? 100;
1636
1666
  const groupTop = section.groupTop ?? opts.top ?? 0;
1637
- const style = `position:absolute;left:${mm(tableLeft)};top:${mm(groupTop)};width:${mm(tableWidth)};overflow:visible;`;
1667
+ const tableBottom = tableDesignBottom(el);
1668
+ const zStyle = typeof opts.zIndex === "number" ? `z-index:${opts.zIndex};` : "";
1669
+ const style = `position:absolute;left:${mm(tableLeft)};top:${mm(groupTop)};width:${mm(tableWidth)};overflow:visible;${zStyle}`;
1638
1670
  const startRow = section.startRow ?? 0;
1639
1671
  const endRow = section.endRow ?? 0;
1640
1672
  let sliceHtml = "";
@@ -1650,31 +1682,39 @@ function renderFlowGroup(el, section, template, ctx) {
1650
1682
  ${bodyHtml}${subtotalHtml}${summaryHtml}`)}
1651
1683
  </div>`;
1652
1684
  }
1653
- let cursorBottom = tableDesignBottom(el);
1654
- const followHtml = (section.followElementIds ?? []).map((id) => {
1685
+ const followIds = section.followElementIds ?? [];
1686
+ const followExtent = followIds.reduce((maxBottom, id) => {
1687
+ const m = findElement(template, id);
1688
+ if (!m) return maxBottom;
1689
+ return Math.max(maxBottom, (m.options?.top ?? 0) + (m.options?.height ?? 0));
1690
+ }, tableBottom);
1691
+ const followHtml = followIds.map((id) => {
1655
1692
  const m = findElement(template, id);
1656
1693
  if (!m) return "";
1657
1694
  const mTop = m.options?.top ?? 0;
1658
1695
  const mLeft = m.options?.left ?? 0;
1659
1696
  const mWidth = m.options?.width ?? tableWidth;
1660
1697
  const mHeight = m.options?.height ?? 0;
1661
- const gap = Math.max(mTop - cursorBottom, 0);
1662
- cursorBottom = mTop + mHeight;
1698
+ const mZ = m.options?.zIndex;
1663
1699
  const type = m.type || m.printElementType?.type || "text";
1664
1700
  const needsHeight = type === "rect" || type === "oval" || type === "image";
1665
1701
  const flowStyle = [
1666
- "position:relative",
1702
+ "position:absolute",
1667
1703
  `left:${mm(mLeft - tableLeft)}`,
1704
+ `top:${mm(mTop - tableBottom)}`,
1668
1705
  `width:${mm(mWidth)}`,
1669
1706
  ...needsHeight && mHeight > 0 ? [`height:${mm(mHeight)}`] : [],
1670
- `margin-top:${mm(gap)}`,
1707
+ ...typeof mZ === "number" ? [`z-index:${mZ}`] : [],
1671
1708
  "overflow:visible"
1672
1709
  ].join(";") + ";";
1673
1710
  return renderElement(m, false, flowStyle, void 0, ctx);
1674
1711
  }).join("\n");
1712
+ const followWrap = followIds.length > 0 ? `<div class="flow-follow" style="position:relative;width:${mm(tableWidth)};height:${mm(Math.max(followExtent - tableBottom, 0))};overflow:visible;">
1713
+ ${followHtml}
1714
+ </div>` : "";
1675
1715
  return `<div class="flow-group" style="${style}">
1676
1716
  ${sliceHtml}
1677
- ${followHtml}
1717
+ ${followWrap}
1678
1718
  </div>`;
1679
1719
  }
1680
1720
  function renderAreaElements(elements, _contentWidth, pageIndex, totalPages, ctx) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@worm-vue3-print/core",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
4
4
  "type": "module",
5
5
  "description": "打印模板表达式引擎与同构渲染管线(HTML 生成/分页/数据绑定),无 Vue、无宿主依赖",
6
6
  "main": "dist/index.cjs",