gm-printer 10.42.3-beta.1 → 10.42.3-beta.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm-printer",
3
- "version": "10.42.3-beta.1",
3
+ "version": "10.42.3-beta.2",
4
4
  "description": "diy printer",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -106,7 +106,8 @@ export const flattenHorizontalPackedRows = (rows, colNumber) => {
106
106
  export const flattenVerticalSegment = (rows, colNumber) => {
107
107
  const columns = Array.from({ length: colNumber }, () => [])
108
108
  rows.forEach(row => {
109
- if (isSpecialTableRow(row)) return
109
+ // _isEmptyData 行的序号可能被填充过,必须显式跳过,否则会被当真实商品吸收
110
+ if (isSpecialTableRow(row) || row._isEmptyData) return
110
111
  splitPackedRow(row, colNumber).forEach((item, col) => {
111
112
  if (isRealItem(item)) {
112
113
  columns[col].push(stripMultiSuffixFields(item))
@@ -116,6 +117,37 @@ export const flattenVerticalSegment = (rows, colNumber) => {
116
117
  return columns.reduce((list, col) => list.concat(col), [])
117
118
  }
118
119
 
120
+ /** 按特殊行(分类小计/标题等)把整表切成若干「组」:相邻非特殊行的 run */
121
+ export const splitRowsIntoGroups = rows => {
122
+ const groups = []
123
+ let start = -1
124
+ for (let i = 0; i <= (rows || []).length; i++) {
125
+ const isBoundary = i === rows.length || isSpecialTableRow(rows[i])
126
+ if (!isBoundary && start < 0) {
127
+ start = i
128
+ }
129
+ if (isBoundary) {
130
+ if (start >= 0) {
131
+ groups.push({ begin: start, end: i })
132
+ }
133
+ start = -1
134
+ }
135
+ }
136
+ return groups
137
+ }
138
+
139
+ /**
140
+ * 源表(单栏/横向多栏)按特殊行分组后逐组拍平 → 每组一个商品队列。
141
+ * 源表不会被 repack 改写,队列是纯函数,重复执行结果不变(幂等)。
142
+ */
143
+ export const buildSourceGroupQueues = (rows, colNumber) =>
144
+ splitRowsIntoGroups(rows).map(group =>
145
+ flattenHorizontalPackedRows(
146
+ (rows || []).slice(group.begin, group.end),
147
+ colNumber
148
+ )
149
+ )
150
+
119
151
  const assignItemToRow = (row, item, suffix) => {
120
152
  const clean = stripMultiSuffixFields(item) || {}
121
153
  Object.keys(clean).forEach(key => {
@@ -442,39 +474,34 @@ export const renumberSegmentSerialsColumnMajor = (
442
474
 
443
475
  /**
444
476
  * 按分页区间把纵向多栏数据改成「每页先左后右」。
445
- * - 商品按序号灌满当前段左列再排右列(含空行的整段行数)
446
- * - 每一行都是新建对象,清除旧右列残留
447
- * - fillIndex 时整段按列重编序号,有数据/无数据同一套规则
477
+ * - 按特殊行(分类小计等)把整表切成若干组,每组一个商品队列:
478
+ * 商品只从本组队列取数,永不跨组 —— 奇数商品的组余数留在本组末段空格,
479
+ * 不会挤占下一组(修复分类合并错位)
480
+ * - 每组队列优先用外部传入的源表顺序(groupQueues;源表不被 repack 改写,
481
+ * 重复执行幂等);缺失时按「各页段贡献」从当前行拼接(重排的不动点,
482
+ * 同样幂等,但首趟不做每页重排)
483
+ * - 特殊行钉在原地;非特殊行都是新建对象,清除旧右列残留
484
+ * - fillIndex 时整段按列重编序号,有数据/无数据同一套规则,序号跨段跨组连续
448
485
  */
449
486
  export const repackVerticalTableByPageRanges = (
450
487
  rows,
451
488
  colNumber,
452
489
  pageRanges,
453
490
  fillIndex = false,
454
- sourceItems
491
+ groupQueues = null
455
492
  ) => {
456
493
  if (!rows?.length || !pageRanges?.length || colNumber < 2) return rows
457
- const items = (sourceItems && sourceItems.length
458
- ? sourceItems
459
- : flattenVerticalSegment(rows, colNumber)
460
- ).map(stripMultiSuffixFields)
461
494
 
462
- if (!items.length && !fillIndex) return rows
463
-
464
- // 全新数组,避免与旧行对象共享引用
465
- const result = new Array(rows.length)
466
- for (let k = 0; k < rows.length; k++) {
467
- result[k] = rows[k]
468
- }
495
+ const groups = splitRowsIntoGroups(rows)
496
+ // 全新数组,避免与旧行对象共享引用;特殊行沿用原引用(钉原地)
497
+ const result = rows.slice()
469
498
 
470
- let consumed = 0
471
- let nextSerial = 1
472
- pageRanges.forEach(({ begin, end }) => {
499
+ // 页 range 与组边界取交集得到页段,并记录所属组
500
+ const segments = pageRanges.reduce((list, { begin, end }) => {
473
501
  const packEnd = Math.min(end, rows.length)
474
502
  let i = begin
475
503
  while (i < packEnd) {
476
504
  if (isSpecialTableRow(rows[i])) {
477
- result[i] = rows[i]
478
505
  i++
479
506
  continue
480
507
  }
@@ -482,32 +509,60 @@ export const repackVerticalTableByPageRanges = (
482
509
  while (j < packEnd && !isSpecialTableRow(rows[j])) {
483
510
  j++
484
511
  }
485
-
486
- const rowCount = j - i
487
- if (rowCount <= 0) {
488
- i = j
489
- continue
512
+ if (j > i) {
513
+ list.push({
514
+ begin: i,
515
+ end: j,
516
+ groupIndex: groups.findIndex(g => i >= g.begin && i < g.end)
517
+ })
490
518
  }
519
+ i = j
520
+ }
521
+ return list
522
+ }, [])
523
+
524
+ const externalQueues = groupQueues || []
525
+ const queues = groups.map((_, gi) => {
526
+ const external = externalQueues[gi]
527
+ if (external && external.length) {
528
+ return external
529
+ }
530
+ return segments
531
+ .filter(s => s.groupIndex === gi)
532
+ .reduce(
533
+ (list, s) =>
534
+ list.concat(
535
+ flattenVerticalSegment(rows.slice(s.begin, s.end), colNumber)
536
+ ),
537
+ []
538
+ )
539
+ })
491
540
 
492
- const capacity = rowCount * colNumber
493
- const chunk = items.slice(consumed, consumed + capacity)
494
- const packed = packVerticalNewspaper(chunk, colNumber, rowCount, false)
495
- packed.forEach((row, offset) => {
496
- result[i + offset] = row
497
- })
498
- consumed += chunk.length
499
-
500
- if (fillIndex) {
501
- nextSerial = renumberSegmentSerialsColumnMajor(
502
- result,
503
- i,
504
- j,
505
- colNumber,
506
- nextSerial
507
- )
508
- }
541
+ if (!queues.some(q => q.length) && !fillIndex) return rows
509
542
 
510
- i = j
543
+ const consumed = groups.map(() => 0)
544
+ let nextSerial = 1
545
+ segments.forEach(({ begin, end, groupIndex }) => {
546
+ const rowCount = end - begin
547
+ const queue = queues[groupIndex]
548
+ const chunk = queue.slice(
549
+ consumed[groupIndex],
550
+ consumed[groupIndex] + rowCount * colNumber
551
+ )
552
+ consumed[groupIndex] += chunk.length
553
+ packVerticalNewspaper(chunk, colNumber, rowCount, false).forEach(
554
+ (row, offset) => {
555
+ result[begin + offset] = row
556
+ }
557
+ )
558
+ if (fillIndex) {
559
+ nextSerial = renumberSegmentSerialsColumnMajor(
560
+ result,
561
+ begin,
562
+ end,
563
+ colNumber,
564
+ nextSerial
565
+ )
511
566
  }
512
567
  })
513
568
  return result
@@ -30,7 +30,9 @@ import {
30
30
  packHorizontalRowMajor,
31
31
  createEmptyRows,
32
32
  createEmptyRowSkeleton,
33
- resolveVerticalEmptyCount
33
+ isSpecialTableRow,
34
+ splitRowsIntoGroups,
35
+ buildSourceGroupQueues
34
36
  } from './multi_column'
35
37
 
36
38
  export const TR_BASE_HEIGHT = 23
@@ -760,13 +762,28 @@ class PrinterStore {
760
762
  // 无商品时仍可能需要给空行按列编序号
761
763
  if (!sourceItems.length && !this.fillIndex) return
762
764
 
765
+ // 源表按特殊行分组 → 每组一个商品队列:商品永不跨分类小计行
766
+ const groupQueues = buildSourceGroupQueues(sourceRows, sourceColNumber)
767
+ const verticalGroups = splitRowsIntoGroups(verticalRows)
768
+ const lastGroup = verticalGroups[verticalGroups.length - 1]
769
+ const lastGroupAllEmpty =
770
+ lastGroup &&
771
+ verticalRows
772
+ .slice(lastGroup.begin, lastGroup.end)
773
+ .every(row => row?._isEmptyData)
774
+ // 组数对齐(允许纵向表多一个纯空行尾组=行数填充产物)才用源表队列;
775
+ // 否则退回「按页段贡献」兜底:最坏不做每页重排,绝不会跨组串货
776
+ const aligned =
777
+ groupQueues.length === verticalGroups.length ||
778
+ (groupQueues.length === verticalGroups.length - 1 && lastGroupAllEmpty)
779
+
763
780
  const pageRanges = this.getTablePageRanges(contentIndex, verticalRows)
764
781
  this.data._table[verticalKey] = repackVerticalTableByPageRanges(
765
782
  verticalRows,
766
783
  colNumber,
767
784
  pageRanges,
768
785
  this.fillIndex,
769
- sourceItems
786
+ aligned ? groupQueues : null
770
787
  )
771
788
  this.dataRevision += 1
772
789
  })
@@ -1072,44 +1089,16 @@ class PrinterStore {
1072
1089
  const emptyCount =
1073
1090
  emptyTable.length > 0 ? emptyTable.length : toFillEmptyCount
1074
1091
 
1075
- // 纵向多栏:源商品按对半行落位 + 追加空行;左列优先由 repack 按「数据行+空行」整段完成
1092
+ // 纵向多栏:保留真实行(含分类小计等特殊行),仅追加空行补页;
1093
+ // 每页先左后右、按组落位由随后的 repackVerticalMultiByPages 完成
1076
1094
  if (/_vertical$/.test(dataKey)) {
1077
1095
  const sourceKey = dataKey.replace(/_vertical$/, '')
1078
- const singleKey = sourceKey.replace(/_multi3?/, '')
1079
1096
  const colNumber = getMultiNumber(sourceKey)
1080
- // 横向源表缺失时先生成(双栏→三栏常见)
1081
- if (!this.data._table[sourceKey]?.length) {
1082
- this.ensureMultiTableData(sourceKey)
1083
- }
1084
- const sourceRows =
1085
- this.data._table[singleKey] || this.data._table[sourceKey] || []
1086
- const sourceColNumber = isMultiTable(singleKey)
1087
- ? getMultiNumber(singleKey)
1088
- : isMultiTable(sourceKey)
1089
- ? colNumber
1090
- : 1
1091
- const sourceItems = flattenHorizontalPackedRows(
1092
- sourceRows,
1093
- sourceColNumber
1094
- )
1095
- const baseRowCount = sourceItems.length
1096
- ? Math.max(1, Math.ceil(sourceItems.length / colNumber))
1097
- : 0
1098
- // 钉死总行:left-first repack 后 _isEmptyData 变少,不能用 emptyTable.length 回推
1099
- const emptyCount = resolveVerticalEmptyCount(
1100
- tableData.length,
1101
- baseRowCount,
1102
- emptyTable.length > 0
1103
- ? emptyTable.length
1104
- : this.getFilledTableData(table).length,
1105
- emptyTable.length > 0
1097
+ const sample =
1098
+ table.find(row => row && !isSpecialTableRow(row)) || tableData[0]
1099
+ this.data._table[dataKey] = table.concat(
1100
+ createEmptyRows(emptyCount, sample, colNumber)
1106
1101
  )
1107
- const dataRows = baseRowCount
1108
- ? packVerticalNewspaper(sourceItems, colNumber, baseRowCount, false)
1109
- : []
1110
- const sample = dataRows[0] || sourceRows[0]
1111
- const emptyRows = createEmptyRows(emptyCount, sample, colNumber)
1112
- this.data._table[dataKey] = dataRows.concat(emptyRows)
1113
1102
  return
1114
1103
  }
1115
1104