gm-printer 10.42.3-beta.0 → 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/build/demo.js +9 -9
- package/package.json +1 -1
- package/src/common/editor_store.js +21 -29
- package/src/printer/compute_pages.js +2 -1
- package/src/printer/multi_column.js +98 -43
- package/src/printer/printer.js +7 -0
- package/src/printer/store.js +148 -70
package/package.json
CHANGED
|
@@ -263,24 +263,6 @@ class EditorStore {
|
|
|
263
263
|
this.remainPageHeihgt = remainHeight
|
|
264
264
|
}
|
|
265
265
|
|
|
266
|
-
@action
|
|
267
|
-
getFilledTableData(tableData) {
|
|
268
|
-
const { autoFillConfig } = this.config
|
|
269
|
-
if (!this.selectedRegion && !autoFillConfig?.checked) return []
|
|
270
|
-
let tr_count = 0
|
|
271
|
-
|
|
272
|
-
tr_count = Math.floor(
|
|
273
|
-
this.remainPageHeihgt / this.computedTableCustomerRowHeight
|
|
274
|
-
)
|
|
275
|
-
if (tr_count <= 0) return []
|
|
276
|
-
|
|
277
|
-
const sample = (tableData || []).find(item => item && !item._isEmptyData)
|
|
278
|
-
const dataKey =
|
|
279
|
-
this.computedTableSpecialConfig?.dataKey || autoFillConfig?.dataKey
|
|
280
|
-
const colNumber = detectColNumber(dataKey, tableData)
|
|
281
|
-
return createEmptyRows(tr_count, sample, colNumber)
|
|
282
|
-
}
|
|
283
|
-
|
|
284
266
|
/** 多栏表缺失时(如双栏切三栏),从单栏/双栏源表现场打包到 mockData */
|
|
285
267
|
@action
|
|
286
268
|
ensureMockMultiTable(dataKey) {
|
|
@@ -345,18 +327,14 @@ class EditorStore {
|
|
|
345
327
|
this.ensureMockMultiTable(getDataKey(dataKey, 'vertical'))
|
|
346
328
|
}
|
|
347
329
|
|
|
330
|
+
// 开启时不在编辑器侧预推空行:预推依赖挂载时上报的剩余高度,布局(纸张/表格/
|
|
331
|
+
// 排列)变化后会过期,第一次开启填充会按旧基准多推,把已渲染完的表格撑到下一页。
|
|
332
|
+
// 空行统一由 Printer 侧「先分页拿基准 → 补空 → 重分页」写入(见 printer.js)
|
|
348
333
|
const syncEmptyRows = key => {
|
|
349
334
|
if (!this.mockData._table?.[key]) return
|
|
350
|
-
const list = this.mockData._table[key]
|
|
351
|
-
const hasHadEmptyData = _.some(list, data => data?._isEmptyData)
|
|
352
|
-
if (isAutoFilling && !hasHadEmptyData) {
|
|
353
|
-
list.push(...this.getFilledTableData(list))
|
|
354
|
-
}
|
|
355
335
|
if (!isAutoFilling) {
|
|
356
336
|
this.clearExtraTableData(key)
|
|
357
|
-
return
|
|
358
337
|
}
|
|
359
|
-
this.mockData._table[key] = list
|
|
360
338
|
}
|
|
361
339
|
|
|
362
340
|
const numberEmptyRows = key => {
|
|
@@ -385,13 +363,12 @@ class EditorStore {
|
|
|
385
363
|
const baseRowCount = sourceItems.length
|
|
386
364
|
? Math.max(1, Math.ceil(sourceItems.length / colNumber))
|
|
387
365
|
: 0
|
|
388
|
-
// 钉死总行:repack 后 _isEmptyData 会变少,不能再用 emptyTable.length
|
|
366
|
+
// 钉死总行:repack 后 _isEmptyData 会变少,不能再用 emptyTable.length;
|
|
367
|
+
// 无空行时不预推,交由 Printer 按当前分页基准补
|
|
389
368
|
const emptyCount = resolveVerticalEmptyCount(
|
|
390
369
|
list.length,
|
|
391
370
|
baseRowCount,
|
|
392
|
-
emptyTable.length
|
|
393
|
-
? emptyTable.length
|
|
394
|
-
: this.getFilledTableData(list).length,
|
|
371
|
+
emptyTable.length,
|
|
395
372
|
emptyTable.length > 0
|
|
396
373
|
)
|
|
397
374
|
const dataRows = baseRowCount
|
|
@@ -497,6 +474,11 @@ class EditorStore {
|
|
|
497
474
|
@action.bound
|
|
498
475
|
setPageSize(field, value) {
|
|
499
476
|
this.config.page.size[field] = value
|
|
477
|
+
// 页面几何变化会让预览 Printer 重挂载;行数填充开启时清掉按旧页高算出的空行,
|
|
478
|
+
// 重挂载后按新几何重补(对齐切换商品排列时的处理,见 setTableDataKeyEffect)
|
|
479
|
+
if (this.isAutoFilling || this.config?.autoFillConfig?.checked) {
|
|
480
|
+
this.clearAllTableEmptyData()
|
|
481
|
+
}
|
|
500
482
|
}
|
|
501
483
|
|
|
502
484
|
@computed
|
|
@@ -548,6 +530,10 @@ class EditorStore {
|
|
|
548
530
|
height: size.width
|
|
549
531
|
}
|
|
550
532
|
}
|
|
533
|
+
// 可用页高变了,行数填充开启时清掉按旧方向算出的空行,重挂载后重补
|
|
534
|
+
if (this.isAutoFilling || this.config?.autoFillConfig?.checked) {
|
|
535
|
+
this.clearAllTableEmptyData()
|
|
536
|
+
}
|
|
551
537
|
}
|
|
552
538
|
}
|
|
553
539
|
|
|
@@ -588,6 +574,12 @@ class EditorStore {
|
|
|
588
574
|
gap,
|
|
589
575
|
name
|
|
590
576
|
}
|
|
577
|
+
// 纸张类型/内边距变了,可用页高随之变化;行数填充开启时清掉按旧纸张算出的
|
|
578
|
+
// 空行(fillTableEmptyIndex 对已有空行沿用旧数量不重算,旧数量会把同页后续
|
|
579
|
+
// 表格挤到下一页),交给 Printer 重挂载后按新页高重补
|
|
580
|
+
if (this.isAutoFilling || this.config?.autoFillConfig?.checked) {
|
|
581
|
+
this.clearAllTableEmptyData()
|
|
582
|
+
}
|
|
591
583
|
}
|
|
592
584
|
|
|
593
585
|
@action
|
|
@@ -62,7 +62,8 @@ function init() {
|
|
|
62
62
|
* 影响合计行渲染与行高,进而影响分页边界)
|
|
63
63
|
* @returns {Promise<{pages: Array, remainPageHeight: number, tableCustomerRowHeight: number}>}
|
|
64
64
|
* pages: [[{type, index, begin, end}, ...], ...] 对 data._table[contents[i].dataKey] 行号的切片
|
|
65
|
-
* remainPageHeight:
|
|
65
|
+
* remainPageHeight: 行数填充基准高度(填充表格最后一段所在页的剩余高度,
|
|
66
|
+
* 无填充表格时为末页剩余;供行数填充换算)
|
|
66
67
|
* tableCustomerRowHeight: 表格行高(供行数填充换算)
|
|
67
68
|
*/
|
|
68
69
|
function computePages({ data, config, isSomeSubtotalTr }) {
|
|
@@ -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
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
471
|
-
|
|
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
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
package/src/printer/printer.js
CHANGED
|
@@ -100,6 +100,13 @@ class Printer extends React.Component {
|
|
|
100
100
|
await this.props.printerStore.setFillIndex(nextProps.fillIndex)
|
|
101
101
|
await this.props.printerStore.setData(nextProps.data)
|
|
102
102
|
await this.props.printerStore.setReady(true)
|
|
103
|
+
// 与 componentDidMount 同序:先按当前数据分页拿到行数填充基准,再补空行、
|
|
104
|
+
// 按补空后的数据重新分页。缺前置分页时 changeTableData 只能用上一轮
|
|
105
|
+
// (可能已含空行)的 remainPageHeight 换算行数,开关填充时行数会偏小或偏大
|
|
106
|
+
await this.props.printerStore.computedPages()
|
|
107
|
+
// 把「未补空」基准回报给编辑器,与挂载流程同口径
|
|
108
|
+
this.props.getremainpageHeight &&
|
|
109
|
+
this.props.getremainpageHeight(this.props.printerStore.remainPageHeight)
|
|
103
110
|
await this.props.printerStore.changeTableData()
|
|
104
111
|
await this.props.printerStore.computedPages()
|
|
105
112
|
}
|
package/src/printer/store.js
CHANGED
|
@@ -30,7 +30,9 @@ import {
|
|
|
30
30
|
packHorizontalRowMajor,
|
|
31
31
|
createEmptyRows,
|
|
32
32
|
createEmptyRowSkeleton,
|
|
33
|
-
|
|
33
|
+
isSpecialTableRow,
|
|
34
|
+
splitRowsIntoGroups,
|
|
35
|
+
buildSourceGroupQueues
|
|
34
36
|
} from './multi_column'
|
|
35
37
|
|
|
36
38
|
export const TR_BASE_HEIGHT = 23
|
|
@@ -68,7 +70,7 @@ class PrinterStore {
|
|
|
68
70
|
|
|
69
71
|
@observable pages = [] // [{type, index, begin, end}]
|
|
70
72
|
|
|
71
|
-
/**
|
|
73
|
+
/** 行数填充基准:填充表格最后一段所在页的剩余空白高度;无填充表格时为末页剩余 */
|
|
72
74
|
@observable remainPageHeight = 0
|
|
73
75
|
@observable isSave = false
|
|
74
76
|
/** data._table 非 observable,变更后递增以驱动预览重渲 */
|
|
@@ -327,6 +329,8 @@ class PrinterStore {
|
|
|
327
329
|
|
|
328
330
|
// 使用本地数组累积分页结果,避免循环中反复触发 observable 更新
|
|
329
331
|
const pages = []
|
|
332
|
+
// 各表格的行高数组(含明细拆分 splice 后的结果),供行数填充基准按同一口径复算
|
|
333
|
+
const splicedHeightsMap = {}
|
|
330
334
|
// 每页必有 页眉header, 页脚footer , 签名
|
|
331
335
|
const allPagesHaveThisHeight = heightMap.header + heightMap.footer
|
|
332
336
|
// 退出计算! 因为页眉 + 页脚 > currentPageHeight,页面装不下其他东西
|
|
@@ -362,37 +366,9 @@ class PrinterStore {
|
|
|
362
366
|
index++
|
|
363
367
|
continue
|
|
364
368
|
}
|
|
365
|
-
const {
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
summaryConfig,
|
|
369
|
-
overallOrder,
|
|
370
|
-
diySubtotal
|
|
371
|
-
} = content
|
|
372
|
-
// 如果显示每页合计,那么table高度多预留一行高度
|
|
373
|
-
const subtotalTrHeight = subtotal.show ? getSumTrHeight(subtotal) : 0
|
|
374
|
-
// 如果显示整单合计,那么table高度多预留一行高度
|
|
375
|
-
const overallOrderTrHeight = overallOrder?.show
|
|
376
|
-
? getOverallOrderTrHeight(overallOrder)
|
|
377
|
-
: 0
|
|
378
|
-
// 如果每页合计(新的),那么table高度多预留一行高度
|
|
379
|
-
const pageSummaryTrHeight =
|
|
380
|
-
summaryConfig?.pageSummaryShow && !isMultiTable(dataKey) // 双栏table没有每页合计
|
|
381
|
-
? getSumTrHeight(summaryConfig)
|
|
382
|
-
: 0
|
|
383
|
-
|
|
384
|
-
// 如果显示自定义每页合计,那么table高度多预留一行高度
|
|
385
|
-
const diySubtotalTrHeight = diySubtotal?.show
|
|
386
|
-
? getOverallOrderTrHeight(diySubtotal) // 使用相同的计算方式
|
|
387
|
-
: 0
|
|
388
|
-
|
|
389
|
-
// 每个表格都具有的高度
|
|
390
|
-
const allTableHaveThisHeight =
|
|
391
|
-
table.head.height +
|
|
392
|
-
subtotalTrHeight +
|
|
393
|
-
pageSummaryTrHeight +
|
|
394
|
-
overallOrderTrHeight +
|
|
395
|
-
diySubtotalTrHeight
|
|
369
|
+
const { dataKey } = content
|
|
370
|
+
// 每个表格都具有的高度(表头 + 各类合计行),提取成方法供填充基准复用同一口径
|
|
371
|
+
const allTableHaveThisHeight = this.getTableAllHaveHeight(index)
|
|
396
372
|
|
|
397
373
|
/** 当前page页面的最小高度 */
|
|
398
374
|
const currentPageMinimumHeight =
|
|
@@ -408,6 +384,8 @@ class PrinterStore {
|
|
|
408
384
|
const heights = [
|
|
409
385
|
...this.getNormalTableBodyHeights(table.body.heights, dataKey)
|
|
410
386
|
]
|
|
387
|
+
// 保留引用:该数组会被明细拆分 splice,填充基准需要用拆分后的行高
|
|
388
|
+
splicedHeightsMap[index] = heights
|
|
411
389
|
const tableData = tableDataMap[dataKey] || []
|
|
412
390
|
// 表格行的索引,用于table.slice(begin, end), 分割到不同页面中
|
|
413
391
|
let begin = 0
|
|
@@ -609,14 +587,127 @@ class PrinterStore {
|
|
|
609
587
|
currentPageHeight += this.getSignHeight()
|
|
610
588
|
}
|
|
611
589
|
|
|
612
|
-
|
|
613
|
-
|
|
590
|
+
// 行数填充基准:优先取「填充表格最后一段所在页」的剩余高度(表格底部与该页
|
|
591
|
+
// 可用底边之间的空白)。旧口径固定取末页剩余,当填充表格不在末页(下方内容
|
|
592
|
+
// 被挤到下一页)时,行数按末页空间换算会超出所在页容量:多出的空行被切到
|
|
593
|
+
// 下一页渲染成空表格,或把同页后续表格挤到下一页。
|
|
594
|
+
// 填充基准不做 Math.round 向上取整,避免整行边界多算 ≤1px 导致重分页时溢出
|
|
595
|
+
const fillRemainHeight = this.computeFillTableRemainHeight(
|
|
596
|
+
pages,
|
|
597
|
+
pageHeight,
|
|
598
|
+
heightMap,
|
|
599
|
+
splicedHeightsMap
|
|
614
600
|
)
|
|
601
|
+
this.remainPageHeight =
|
|
602
|
+
fillRemainHeight != null
|
|
603
|
+
? Math.max(0, fillRemainHeight)
|
|
604
|
+
: Math.max(0, Math.round(pageHeight - currentPageHeight))
|
|
615
605
|
this.isSave = false
|
|
616
606
|
this.config.isSave = false
|
|
617
607
|
this.repackVerticalMultiByPages()
|
|
618
608
|
}
|
|
619
609
|
|
|
610
|
+
/** 表格固定高度:表头 + 每页/整单/自定义合计行(不含数据行),与 computedPages 分页口径一致 */
|
|
611
|
+
getTableAllHaveHeight(contentIndex) {
|
|
612
|
+
const table = this.tablesInfo[`contents.table.${contentIndex}`]
|
|
613
|
+
if (!table?.head) return 0
|
|
614
|
+
const content = this.config.contents[contentIndex]
|
|
615
|
+
const {
|
|
616
|
+
subtotal,
|
|
617
|
+
dataKey,
|
|
618
|
+
summaryConfig,
|
|
619
|
+
overallOrder,
|
|
620
|
+
diySubtotal
|
|
621
|
+
} = content
|
|
622
|
+
// 如果显示每页合计,那么table高度多预留一行高度
|
|
623
|
+
const subtotalTrHeight = subtotal.show ? getSumTrHeight(subtotal) : 0
|
|
624
|
+
// 如果显示整单合计,那么table高度多预留一行高度
|
|
625
|
+
const overallOrderTrHeight = overallOrder?.show
|
|
626
|
+
? getOverallOrderTrHeight(overallOrder)
|
|
627
|
+
: 0
|
|
628
|
+
// 如果每页合计(新的),那么table高度多预留一行高度
|
|
629
|
+
const pageSummaryTrHeight =
|
|
630
|
+
summaryConfig?.pageSummaryShow && !isMultiTable(dataKey) // 双栏table没有每页合计
|
|
631
|
+
? getSumTrHeight(summaryConfig)
|
|
632
|
+
: 0
|
|
633
|
+
// 如果显示自定义每页合计,那么table高度多预留一行高度
|
|
634
|
+
const diySubtotalTrHeight = diySubtotal?.show
|
|
635
|
+
? getOverallOrderTrHeight(diySubtotal) // 使用相同的计算方式
|
|
636
|
+
: 0
|
|
637
|
+
|
|
638
|
+
return (
|
|
639
|
+
table.head.height +
|
|
640
|
+
subtotalTrHeight +
|
|
641
|
+
pageSummaryTrHeight +
|
|
642
|
+
overallOrderTrHeight +
|
|
643
|
+
diySubtotalTrHeight
|
|
644
|
+
)
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
/** 行数填充目标表格在 contents 中的索引(对齐 tableConfig 的解析口径),无法解析返回 null */
|
|
648
|
+
getFillTableContentIndex() {
|
|
649
|
+
const { autoFillConfig } = this.config
|
|
650
|
+
// checked 为 false 时 region 也保留着(编辑器开关来回切),此时同样能定位,
|
|
651
|
+
// 让基准在「未开启填充」的分页里也保持正确口径
|
|
652
|
+
if (!this.selectedRegion && !autoFillConfig?.region) return null
|
|
653
|
+
const region = this.selectedRegion || autoFillConfig.region
|
|
654
|
+
const contentIndex = Number(region.split('.')[2])
|
|
655
|
+
const content = this.config.contents[contentIndex]
|
|
656
|
+
if (!content || !_.has(content, 'dataKey')) return null
|
|
657
|
+
return contentIndex
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
/**
|
|
661
|
+
* 填充表格最后一段所在页的剩余高度(表格底部到该页可用底边的空白)。
|
|
662
|
+
* 高度口径与 computedPages 一致:页眉页脚 + 该页各内容
|
|
663
|
+
* (表格固定高度 + 分段行高 / panel 实测高),所在页为末页时再扣签名区。
|
|
664
|
+
* 定位不到填充表格(未配置 / 表格无数据被跳过)时返回 null,由调用方退回旧口径。
|
|
665
|
+
*/
|
|
666
|
+
computeFillTableRemainHeight(
|
|
667
|
+
pages,
|
|
668
|
+
pageHeight,
|
|
669
|
+
heightMap,
|
|
670
|
+
splicedHeightsMap
|
|
671
|
+
) {
|
|
672
|
+
const fillIndex = this.getFillTableContentIndex()
|
|
673
|
+
if (fillIndex == null || !pages?.length) return null
|
|
674
|
+
|
|
675
|
+
// 定位填充表格最后一个分段所在的页(一个表格每页至多一个分段)
|
|
676
|
+
let pageIndex = -1
|
|
677
|
+
for (let i = pages.length - 1; i >= 0; i--) {
|
|
678
|
+
if (
|
|
679
|
+
(pages[i] || []).some(
|
|
680
|
+
panel => panel?.type === 'table' && panel.index === fillIndex
|
|
681
|
+
)
|
|
682
|
+
) {
|
|
683
|
+
pageIndex = i
|
|
684
|
+
break
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
if (pageIndex < 0) return null
|
|
688
|
+
|
|
689
|
+
// 按分页同一套口径累计该页已用高度:表格下方还有内容时一并计入,
|
|
690
|
+
// 填充就只会补「表格与后续内容之间」的空隙,不会把它们挤到下一页
|
|
691
|
+
let used = heightMap.header + heightMap.footer
|
|
692
|
+
;(pages[pageIndex] || []).forEach(panel => {
|
|
693
|
+
if (panel.type === 'table') {
|
|
694
|
+
const heights = splicedHeightsMap[panel.index] || []
|
|
695
|
+
used += this.getTableAllHaveHeight(panel.index)
|
|
696
|
+
for (let r = panel.begin; r < panel.end; r++) {
|
|
697
|
+
used += heights[r] || 0
|
|
698
|
+
}
|
|
699
|
+
} else {
|
|
700
|
+
used += heightMap[`contents.panel.${panel.index}`] || 0
|
|
701
|
+
}
|
|
702
|
+
})
|
|
703
|
+
|
|
704
|
+
// 签名只渲染在最后一页,填充所在页为末页时同样要预留
|
|
705
|
+
const signReserve =
|
|
706
|
+
pageIndex === pages.length - 1 ? this.getSignHeight() : 0
|
|
707
|
+
const remain = pageHeight - used - signReserve
|
|
708
|
+
return Number.isFinite(remain) ? remain : null
|
|
709
|
+
}
|
|
710
|
+
|
|
620
711
|
getTablePageRanges(contentIndex, rows) {
|
|
621
712
|
const ranges = []
|
|
622
713
|
this.pages.forEach(page => {
|
|
@@ -671,13 +762,28 @@ class PrinterStore {
|
|
|
671
762
|
// 无商品时仍可能需要给空行按列编序号
|
|
672
763
|
if (!sourceItems.length && !this.fillIndex) return
|
|
673
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
|
+
|
|
674
780
|
const pageRanges = this.getTablePageRanges(contentIndex, verticalRows)
|
|
675
781
|
this.data._table[verticalKey] = repackVerticalTableByPageRanges(
|
|
676
782
|
verticalRows,
|
|
677
783
|
colNumber,
|
|
678
784
|
pageRanges,
|
|
679
785
|
this.fillIndex,
|
|
680
|
-
|
|
786
|
+
aligned ? groupQueues : null
|
|
681
787
|
)
|
|
682
788
|
this.dataRevision += 1
|
|
683
789
|
})
|
|
@@ -983,44 +1089,16 @@ class PrinterStore {
|
|
|
983
1089
|
const emptyCount =
|
|
984
1090
|
emptyTable.length > 0 ? emptyTable.length : toFillEmptyCount
|
|
985
1091
|
|
|
986
|
-
//
|
|
1092
|
+
// 纵向多栏:保留真实行(含分类小计等特殊行),仅追加空行补页;
|
|
1093
|
+
// 每页先左后右、按组落位由随后的 repackVerticalMultiByPages 完成
|
|
987
1094
|
if (/_vertical$/.test(dataKey)) {
|
|
988
1095
|
const sourceKey = dataKey.replace(/_vertical$/, '')
|
|
989
|
-
const singleKey = sourceKey.replace(/_multi3?/, '')
|
|
990
1096
|
const colNumber = getMultiNumber(sourceKey)
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
const sourceRows =
|
|
996
|
-
this.data._table[singleKey] || this.data._table[sourceKey] || []
|
|
997
|
-
const sourceColNumber = isMultiTable(singleKey)
|
|
998
|
-
? getMultiNumber(singleKey)
|
|
999
|
-
: isMultiTable(sourceKey)
|
|
1000
|
-
? colNumber
|
|
1001
|
-
: 1
|
|
1002
|
-
const sourceItems = flattenHorizontalPackedRows(
|
|
1003
|
-
sourceRows,
|
|
1004
|
-
sourceColNumber
|
|
1005
|
-
)
|
|
1006
|
-
const baseRowCount = sourceItems.length
|
|
1007
|
-
? Math.max(1, Math.ceil(sourceItems.length / colNumber))
|
|
1008
|
-
: 0
|
|
1009
|
-
// 钉死总行:left-first repack 后 _isEmptyData 变少,不能用 emptyTable.length 回推
|
|
1010
|
-
const emptyCount = resolveVerticalEmptyCount(
|
|
1011
|
-
tableData.length,
|
|
1012
|
-
baseRowCount,
|
|
1013
|
-
emptyTable.length > 0
|
|
1014
|
-
? emptyTable.length
|
|
1015
|
-
: this.getFilledTableData(table).length,
|
|
1016
|
-
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)
|
|
1017
1101
|
)
|
|
1018
|
-
const dataRows = baseRowCount
|
|
1019
|
-
? packVerticalNewspaper(sourceItems, colNumber, baseRowCount, false)
|
|
1020
|
-
: []
|
|
1021
|
-
const sample = dataRows[0] || sourceRows[0]
|
|
1022
|
-
const emptyRows = createEmptyRows(emptyCount, sample, colNumber)
|
|
1023
|
-
this.data._table[dataKey] = dataRows.concat(emptyRows)
|
|
1024
1102
|
return
|
|
1025
1103
|
}
|
|
1026
1104
|
|