gm-printer 10.41.9 → 10.42.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm-printer",
3
- "version": "10.41.9",
3
+ "version": "10.42.1",
4
4
  "description": "diy printer",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -19,14 +19,33 @@ export function buildDataKey(prefix, suffix) {
19
19
  return `${prefix}${suffix}`
20
20
  }
21
21
 
22
- /** 下拉选中「按明细单行」时的 value(detail_one_row) */
22
+ /** 下拉选中「按明细单行」时的 value(detail_one_row),可带 category/multi 等后缀 */
23
23
  export function isDetailOneRowSelectKey(dataKey) {
24
- return getDataKeySuffix(dataKey) === 'detail_one_row'
24
+ const suffix = getDataKeySuffix(dataKey)
25
+ return suffix === 'detail_one_row' || suffix.startsWith('detail_one_row_')
25
26
  }
26
27
 
28
+ /** 独立滚动明细单行(sku/address),可带 category/multi 等后缀 */
27
29
  export function isIndependentRolDataKey(dataKey) {
28
30
  const suffix = getDataKeySuffix(dataKey)
29
- return suffix === 'independent_rol_sku' || suffix === 'independent_rol_address'
31
+ return (
32
+ suffix === 'independent_rol_sku' ||
33
+ suffix === 'independent_rol_address' ||
34
+ suffix.startsWith('independent_rol_sku_') ||
35
+ suffix.startsWith('independent_rol_address_')
36
+ )
37
+ }
38
+
39
+ /**
40
+ * 从现有 dataKey 抽出可叠加修饰后缀(category / multi)
41
+ * 用于切换 goods/merchant 排序时保留右键「商品分类」「双栏」状态
42
+ */
43
+ export function getDataKeyModifierSuffix(dataKey) {
44
+ const parts = getDataKeySuffix(dataKey).split('_').filter(Boolean)
45
+ const modifiers = []
46
+ if (parts.includes('category')) modifiers.push('category')
47
+ if (parts.includes('multi')) modifiers.push('multi')
48
+ return modifiers.length ? `_${modifiers.join('_')}` : ''
30
49
  }
31
50
 
32
51
  export function isLastColDataKey(dataKey) {
@@ -24,6 +24,9 @@ class EditorCutomizedConfig extends React.Component {
24
24
  handleFillIndex = value => {
25
25
  const { editStore } = this.props
26
26
  editStore.setFillIndex(value)
27
+ if (editStore.isAutoFilling) {
28
+ editStore.handleChangeTableData(true)
29
+ }
27
30
  }
28
31
 
29
32
  handleTaxFreeProductRateDisplay = e => {
@@ -2,16 +2,37 @@ import i18next from '../../locales'
2
2
  import { action, computed, observable, set, toJS } from 'mobx'
3
3
  import { pageTypeMap } from '../config'
4
4
  import _ from 'lodash'
5
- import { dispatchMsg, getBlockName, exchange, getColSpanLength } from '../util'
5
+ import {
6
+ dispatchMsg,
7
+ getBlockName,
8
+ exchange,
9
+ getColSpanLength,
10
+ isMultiTable,
11
+ getDataKey,
12
+ getMultiNumber
13
+ } from '../util'
6
14
  import { accountRadioList } from './util'
7
15
  import {
8
16
  buildDataKey,
17
+ getDataKeyModifierSuffix,
9
18
  getDataKeyPrefix,
10
19
  isDetailOneRowSelectKey,
11
20
  isIndependentRolDataKey
12
21
  } from './data_key_util'
13
22
  import React from 'react'
14
23
  import { createDiySummaryStoreMethods } from './diy_summary_helper'
24
+ import {
25
+ detectColNumber,
26
+ fillEmptyRowIndex,
27
+ fillPartialLastRow,
28
+ getMaxSerial,
29
+ flattenHorizontalPackedRows,
30
+ packVerticalNewspaper,
31
+ packHorizontalRowMajor,
32
+ createEmptyRows,
33
+ createEmptyRowSkeleton,
34
+ resolveVerticalEmptyCount
35
+ } from '../printer/multi_column'
15
36
 
16
37
  class EditorStore {
17
38
  @observable
@@ -207,6 +228,7 @@ class EditorStore {
207
228
  vv.columns.length +
208
229
  vv.className +
209
230
  vv.dataKey +
231
+ (vv.arrange || 'lateral') +
210
232
  vv.subtotal.show +
211
233
  vv.summaryConfig?.pageSummaryShow +
212
234
  vv.customerRowHeight
@@ -246,14 +268,54 @@ class EditorStore {
246
268
  tr_count = Math.floor(
247
269
  this.remainPageHeihgt / this.computedTableCustomerRowHeight
248
270
  )
271
+ if (tr_count <= 0) return []
249
272
 
250
- const filledData = {
251
- _isEmptyData: true // 表示是填充的空白数据
273
+ const sample = (tableData || []).find(item => item && !item._isEmptyData)
274
+ const dataKey =
275
+ this.computedTableSpecialConfig?.dataKey || autoFillConfig?.dataKey
276
+ const colNumber = detectColNumber(dataKey, tableData)
277
+ return createEmptyRows(tr_count, sample, colNumber)
278
+ }
279
+
280
+ /** 多栏表缺失时(如双栏切三栏),从单栏/双栏源表现场打包到 mockData */
281
+ @action
282
+ ensureMockMultiTable(dataKey) {
283
+ if (!dataKey || !isMultiTable(dataKey)) return
284
+ if (!this.mockData._table) {
285
+ this.mockData._table = {}
286
+ }
287
+ if (this.mockData._table[dataKey]?.length) return
288
+
289
+ const isVertical = /_vertical$/.test(dataKey)
290
+ const sourceKey = dataKey.replace(/_vertical$/, '')
291
+ const colNumber = getMultiNumber(sourceKey)
292
+ const singleKey = sourceKey.replace(/_multi3?/, '')
293
+
294
+ let sourceRows = this.mockData._table[singleKey]
295
+ let sourceColNumber = 1
296
+ if (!sourceRows?.length && /multi3/.test(sourceKey)) {
297
+ const dualKey = sourceKey.replace('multi3', 'multi')
298
+ sourceRows = this.mockData._table[dualKey]
299
+ sourceColNumber = 2
300
+ }
301
+ if (!sourceRows?.length) {
302
+ sourceRows = this.mockData._table[sourceKey]
303
+ sourceColNumber = isMultiTable(sourceKey) ? colNumber : 1
304
+ }
305
+ if (!sourceRows?.length) {
306
+ this.mockData._table[dataKey] = []
307
+ return
252
308
  }
253
- _.map(tableData[0], (val, key) => {
254
- filledData[key] = ''
255
- })
256
- return Array(tr_count).fill(filledData)
309
+
310
+ const sourceItems = flattenHorizontalPackedRows(sourceRows, sourceColNumber)
311
+ const baseRowCount = sourceItems.length
312
+ ? Math.max(1, Math.ceil(sourceItems.length / colNumber))
313
+ : 0
314
+ this.mockData._table[dataKey] = !baseRowCount
315
+ ? []
316
+ : isVertical
317
+ ? packVerticalNewspaper(sourceItems, colNumber, baseRowCount, false)
318
+ : packHorizontalRowMajor(sourceItems, colNumber)
257
319
  }
258
320
 
259
321
  @action.bound
@@ -262,9 +324,6 @@ class EditorStore {
262
324
  if (!this.selectedRegion && !autoFillConfig?.checked) return
263
325
  const dataKey =
264
326
  this.computedTableSpecialConfig?.dataKey || autoFillConfig?.dataKey
265
- // 每次都更新一下?
266
- const table = this.mockData._table[dataKey]
267
- // 赋值一下
268
327
  this.setAutoFillingConfig(isAutoFilling)
269
328
  this.setFillIndex(autoFillConfig?.fillIndex || false)
270
329
  set(this.config, {
@@ -277,24 +336,112 @@ class EditorStore {
277
336
  }
278
337
  })
279
338
 
280
- const hasHadEmptyData = _.some(table, data => data?._isEmptyData)
339
+ if (dataKey && isMultiTable(dataKey)) {
340
+ this.ensureMockMultiTable(dataKey)
341
+ this.ensureMockMultiTable(getDataKey(dataKey, 'vertical'))
342
+ }
281
343
 
282
- // 开关打开,且之前数组不包含空数据,再进行填充
344
+ const syncEmptyRows = key => {
345
+ if (!this.mockData._table?.[key]) return
346
+ const list = this.mockData._table[key]
347
+ const hasHadEmptyData = _.some(list, data => data?._isEmptyData)
348
+ if (isAutoFilling && !hasHadEmptyData) {
349
+ list.push(...this.getFilledTableData(list))
350
+ }
351
+ if (!isAutoFilling) {
352
+ this.clearExtraTableData(key)
353
+ return
354
+ }
355
+ this.mockData._table[key] = list
356
+ }
357
+
358
+ const numberEmptyRows = key => {
359
+ const list = this.mockData._table?.[key]
360
+ if (!list) return
361
+ const table = list.filter(item => !item._isEmptyData)
362
+ const emptyTable = list.filter(item => item._isEmptyData)
363
+ // 纵向表:对半数据行 + 空行占位;左列优先交给 Printer.repack
364
+ if (/_vertical$/.test(key)) {
365
+ const sourceKey = key.replace(/_vertical$/, '')
366
+ const singleKey = sourceKey.replace(/_multi3?/, '')
367
+ const colNumber = getMultiNumber(sourceKey)
368
+ const sourceRows =
369
+ this.mockData._table[singleKey] ||
370
+ this.mockData._table[sourceKey] ||
371
+ []
372
+ const sourceColNumber = isMultiTable(singleKey)
373
+ ? getMultiNumber(singleKey)
374
+ : isMultiTable(sourceKey)
375
+ ? colNumber
376
+ : 1
377
+ const sourceItems = flattenHorizontalPackedRows(
378
+ sourceRows,
379
+ sourceColNumber
380
+ )
381
+ const baseRowCount = sourceItems.length
382
+ ? Math.max(1, Math.ceil(sourceItems.length / colNumber))
383
+ : 0
384
+ // 钉死总行:repack 后 _isEmptyData 会变少,不能再用 emptyTable.length
385
+ const emptyCount = resolveVerticalEmptyCount(
386
+ list.length,
387
+ baseRowCount,
388
+ emptyTable.length > 0
389
+ ? emptyTable.length
390
+ : this.getFilledTableData(list).length,
391
+ emptyTable.length > 0
392
+ )
393
+ const dataRows = baseRowCount
394
+ ? packVerticalNewspaper(sourceItems, colNumber, baseRowCount, false)
395
+ : []
396
+ const sample = dataRows[0] || sourceRows[0]
397
+ this.mockData._table[key] = dataRows.concat(
398
+ createEmptyRows(emptyCount, sample, colNumber)
399
+ )
400
+ return
401
+ }
402
+ const colNumber = detectColNumber(key, list)
403
+ const withPartial = fillPartialLastRow(table, colNumber, this.fillIndex)
404
+ const lastKey = getMaxSerial(withPartial, colNumber) + 1
405
+ const sample = table[0]
406
+ this.mockData._table[key] = withPartial.concat(
407
+ emptyTable.map((item, index) =>
408
+ fillEmptyRowIndex(
409
+ {
410
+ ...createEmptyRowSkeleton(sample, colNumber),
411
+ ...item,
412
+ _isEmptyData: true
413
+ },
414
+ index,
415
+ lastKey,
416
+ colNumber,
417
+ this.fillIndex
418
+ )
419
+ )
420
+ )
421
+ }
283
422
 
284
- if (isAutoFilling && !hasHadEmptyData) {
285
- table.push(...this.getFilledTableData(table))
423
+ syncEmptyRows(dataKey)
424
+ if (dataKey && isMultiTable(dataKey)) {
425
+ const verticalKey = getDataKey(dataKey, 'vertical')
426
+ if (!this.mockData._table[verticalKey]) {
427
+ this.mockData._table[verticalKey] = []
428
+ }
429
+ syncEmptyRows(verticalKey)
286
430
  }
287
- if (!isAutoFilling) {
288
- this.clearExtraTableData(dataKey)
289
- return
431
+ if (isAutoFilling) {
432
+ numberEmptyRows(dataKey)
433
+ if (dataKey && isMultiTable(dataKey)) {
434
+ numberEmptyRows(getDataKey(dataKey, 'vertical'))
435
+ }
290
436
  }
291
- this.mockData._table[dataKey] = table
292
437
  }
293
438
 
294
439
  @action
295
440
  clearExtraTableData(dataKey) {
296
- const newTable = this.mockData._table[dataKey].filter(x => !x._isEmptyData)
297
- this.mockData._table[dataKey] = newTable
441
+ if (!this.mockData._table?.[dataKey]) return
442
+ this.mockData._table[dataKey] = this.mockData._table[dataKey].filter(
443
+ x => !x._isEmptyData
444
+ )
298
445
  }
299
446
 
300
447
  @action
@@ -549,12 +696,39 @@ class EditorStore {
549
696
  for (const [key, table] of Object.entries(tableData)) {
550
697
  tableData[key] = table.filter(x => !x._isEmptyData)
551
698
  }
552
- // this.setAutoFillingConfig(false)
699
+ // 纵向 left-fill 后商品行数 > 对半行且无 _isEmptyData,只滤空行会留下偏多行,
700
+ // 重挂载测高/remain 偏小,多栏切换后空行补不回来。收回到对半商品行。
701
+ Object.keys(tableData).forEach(key => {
702
+ if (!/_vertical$/.test(key) || !isMultiTable(key)) return
703
+ this.collapseVerticalMultiToBaseRows(key)
704
+ })
553
705
  set(this.mockData, {
554
706
  _table: tableData
555
707
  })
556
708
  }
557
709
 
710
+ /** 纵向多栏收到「对半」商品行,去掉 left-fill 撑开的多余行 */
711
+ @action
712
+ collapseVerticalMultiToBaseRows(key) {
713
+ const sourceKey = key.replace(/_vertical$/, '')
714
+ const singleKey = sourceKey.replace(/_multi3?/, '')
715
+ const colNumber = getMultiNumber(sourceKey)
716
+ const sourceRows =
717
+ this.mockData._table[singleKey] || this.mockData._table[sourceKey] || []
718
+ const sourceColNumber = isMultiTable(singleKey)
719
+ ? getMultiNumber(singleKey)
720
+ : isMultiTable(sourceKey)
721
+ ? colNumber
722
+ : 1
723
+ const sourceItems = flattenHorizontalPackedRows(sourceRows, sourceColNumber)
724
+ const baseRowCount = sourceItems.length
725
+ ? Math.max(1, Math.ceil(sourceItems.length / colNumber))
726
+ : 0
727
+ this.mockData._table[key] = baseRowCount
728
+ ? packVerticalNewspaper(sourceItems, colNumber, baseRowCount, false)
729
+ : []
730
+ }
731
+
558
732
  @action
559
733
  addConfigBlock(name, type, pos = {}, link = '') {
560
734
  let blocks
@@ -1023,6 +1197,13 @@ class EditorStore {
1023
1197
 
1024
1198
  this.config.contents[arr[2]].dataKey = newDataKey.join('_')
1025
1199
 
1200
+ // 双栏↔三栏等切换时,mock 可能缺对应 _table,现场兜底生成,避免整表空白
1201
+ const joinedKey = newDataKey.join('_')
1202
+ if (isMultiTable(joinedKey)) {
1203
+ this.ensureMockMultiTable(joinedKey)
1204
+ this.ensureMockMultiTable(getDataKey(joinedKey, 'vertical'))
1205
+ }
1206
+
1026
1207
  // 三级分类相关 token(商品三级分类/三级分类小计)是纯分组、标题能力,
1027
1208
  // 与每页/整单合计可共存,切换时不再强制关闭合计开关;
1028
1209
  // 其余 token(多栏/一级分类/标签等)保持历史行为:切换时强制关闭
@@ -1044,7 +1225,7 @@ class EditorStore {
1044
1225
  autoFillConfig: {
1045
1226
  ...(this.config.autoFillConfig || {}),
1046
1227
  region: this.selectedRegion || this.config.autoFillConfig?.region,
1047
- dataKey: newDataKey.join('_'),
1228
+ dataKey: joinedKey,
1048
1229
  checked: true,
1049
1230
  fillIndex: this.fillIndex
1050
1231
  }
@@ -1393,13 +1574,16 @@ class EditorStore {
1393
1574
  const arr = this.selectedRegion.split('.')
1394
1575
  const table = this.config.contents[arr[2]]
1395
1576
  const prefix = getDataKeyPrefix(table.dataKey)
1577
+ // 切换「按商品/按商户排序」时保留已开启的商品分类、双栏后缀,避免点完分类再切排序把 category 冲掉
1578
+ const baseSuffix = dataKey === 'goods' ? 'independent_rol_sku' : 'independent_rol_address'
1579
+ const nextDataKey = buildDataKey(
1580
+ prefix,
1581
+ `${baseSuffix}${getDataKeyModifierSuffix(table.dataKey)}`
1582
+ )
1396
1583
 
1397
1584
  set(table, {
1398
1585
  purchaseSettingKey: dataKey,
1399
- dataKey:
1400
- dataKey === 'goods'
1401
- ? buildDataKey(prefix, 'independent_rol_sku')
1402
- : buildDataKey(prefix, 'independent_rol_address'),
1586
+ dataKey: nextDataKey,
1403
1587
  detail_sort_type: 0
1404
1588
  })
1405
1589
  }
@@ -1421,7 +1605,8 @@ class EditorStore {
1421
1605
  const table = this.config.contents[arr[2]]
1422
1606
 
1423
1607
  const { dataKey } = table
1424
- if (isIndependentRolDataKey(dataKey)) {
1608
+ // independent_rol_* / detail_one_row_*(含 category、multi 后缀)在下拉里都映射回「按采购明细单行」
1609
+ if (isIndependentRolDataKey(dataKey) || isDetailOneRowSelectKey(dataKey)) {
1425
1610
  return buildDataKey(getDataKeyPrefix(dataKey), 'detail_one_row')
1426
1611
  }
1427
1612
  return dataKey
@@ -1487,10 +1672,26 @@ class EditorStore {
1487
1672
  if (this.selectedRegion) {
1488
1673
  const arr = this.selectedRegion.split('.')
1489
1674
  if (arr.includes('table')) {
1675
+ const table = this.config.contents[arr[2]]
1490
1676
  this.config.contents[arr[2]] = {
1491
- ...this.config.contents[arr[2]],
1677
+ ...table,
1492
1678
  arrange: val
1493
1679
  }
1680
+ // 只改排列并清掉旧空行;保持填充开关,交给 Printer 重挂载后
1681
+ // 「先补空 → 再重新分页」恢复空行(勿在此处用旧 remainHeight 立刻补)
1682
+ if (this.isAutoFilling || this.config?.autoFillConfig?.checked) {
1683
+ this.clearAllTableEmptyData()
1684
+ this.setAutoFillingConfig(true)
1685
+ set(this.config, {
1686
+ autoFillConfig: {
1687
+ ...(this.config.autoFillConfig || {}),
1688
+ region: this.selectedRegion,
1689
+ dataKey: table.dataKey,
1690
+ checked: true,
1691
+ fillIndex: this.fillIndex
1692
+ }
1693
+ })
1694
+ }
1494
1695
  }
1495
1696
  }
1496
1697
  }
@@ -16,7 +16,9 @@ import _ from 'lodash'
16
16
  import { inject, observer } from 'mobx-react'
17
17
  import {
18
18
  buildDataKey,
19
+ getDataKeyModifierSuffix,
19
20
  getDataKeyPrefix,
21
+ getDataKeySuffix,
20
22
  isDetailOneRowSelectKey,
21
23
  } from '../common/data_key_util'
22
24
 
@@ -90,19 +92,23 @@ class TableDetailEditor extends React.Component {
90
92
  .replace(/{{([^.\s}]+)}}/g, '{{列.$1}}')
91
93
  }
92
94
  const prefix = getDataKeyPrefix(config.dataKey)
93
- const rolSkuKey = buildDataKey(prefix, 'independent_rol_sku')
94
- const rolAddressKey = buildDataKey(prefix, 'independent_rol_address')
95
+ const curSuffix = getDataKeySuffix(config.dataKey)
96
+ const modifier = getDataKeyModifierSuffix(config.dataKey)
97
+ // 已是对应 independent_rol_*(含 category/multi)则不再重设,避免冲掉商品分类
95
98
  if (
96
99
  config.purchaseSettingKey === 'goods' &&
97
- config.dataKey !== rolSkuKey
100
+ !curSuffix.startsWith('independent_rol_sku')
98
101
  ) {
99
- editStore.setPurchaseTableKey(rolSkuKey, addFields.specialTableConfig)
102
+ editStore.setPurchaseTableKey(
103
+ buildDataKey(prefix, `independent_rol_sku${modifier}`),
104
+ addFields.specialTableConfig
105
+ )
100
106
  } else if (
101
107
  config.purchaseSettingKey === 'merchant' &&
102
- config.dataKey !== rolAddressKey
108
+ !curSuffix.startsWith('independent_rol_address')
103
109
  ) {
104
110
  editStore.setPurchaseTableKey(
105
- rolAddressKey,
111
+ buildDataKey(prefix, `independent_rol_address${modifier}`),
106
112
  addFields.specialTableConfig
107
113
  )
108
114
  }
package/src/index.js CHANGED
@@ -20,7 +20,8 @@ import {
20
20
  getCSS,
21
21
  getPrintContainerHTML,
22
22
  doBatchFinancePrint,
23
- setPrintStyle
23
+ setPrintStyle,
24
+ computePages
24
25
  } from './printer'
25
26
  import { MULTI_SUFFIX, MULTI_SUFFIX3 } from './config'
26
27
 
@@ -49,5 +50,6 @@ export {
49
50
  MULTI_SUFFIX3,
50
51
  getPrintContainerHTML,
51
52
  doBatchFinancePrint,
52
- setPrintStyle
53
+ setPrintStyle,
54
+ computePages
53
55
  }
@@ -0,0 +1,122 @@
1
+ import React from 'react'
2
+ import ReactDOM from 'react-dom'
3
+ import _ from 'lodash'
4
+ import { toJS } from 'mobx'
5
+ import Printer from './printer'
6
+ import getCSS from './get_css'
7
+ import { afterImgAndSvgLoaded } from '../util'
8
+ import { LONG_PRINT } from '../config'
9
+
10
+ // 与 do_print.js 的容器隔离,使用独立 iframe,避免和打印任务互相 unmount 对方内容
11
+ const printerId = '_gm-printer-compute-pages_' + Math.random()
12
+ let $printer = null
13
+
14
+ /**
15
+ * 初始化隐藏渲染容器
16
+ * 复制自 do_print.js 的 init({ isTest: true }) 分支(iframe 创建 + getCSS 注入),
17
+ * 差异仅 id 前缀与「永远隐藏」。⚠️ get_css / 容器初始化逻辑变更时两处需同步,
18
+ * 否则测量环境与打印环境漂移,分页边界不再一致
19
+ */
20
+ function init() {
21
+ if (!$printer) {
22
+ $printer = window.document.createElement('iframe')
23
+ $printer.id = printerId
24
+ $printer.style.position = 'fixed'
25
+ $printer.style.top = '0'
26
+ $printer.style.width = '100%'
27
+ // 隐藏起来,用户不可见
28
+ $printer.style.left = '-2000px'
29
+ window.document.body.appendChild($printer)
30
+
31
+ const idocument = $printer.contentDocument
32
+ idocument.open()
33
+ idocument.write('<!DOCTYPE html><html><head></head><body></body></html>')
34
+ idocument.close()
35
+
36
+ const doc = $printer.contentWindow.document
37
+
38
+ const style = doc.createElement('style')
39
+ style.appendChild(doc.createTextNode(getCSS()))
40
+ doc.head.appendChild(style)
41
+
42
+ const div = doc.createElement('div')
43
+ div.id = 'appContainer'
44
+
45
+ doc.body.appendChild(div)
46
+ }
47
+ }
48
+
49
+ /**
50
+ * 按打印分页逻辑计算分页结果(不触发打印)
51
+ *
52
+ * 场景:导出功能需要拿到与打印完全一致的分页边界
53
+ *
54
+ * 不支持的模板类型(返回空 pages,调用方需自行降级):
55
+ * - batchPrintConfig === 2(合并打印):componentDidMount 不跑 computedPages,pages 恒空
56
+ * - page.type === LONG_PRINT(长条打印):走 renderLongPage 全量渲染,pages 不对应真实布局
57
+ *
58
+ * @param {Object} options
59
+ * @param {Object} options.data 打印数据(结构同 doPrint 的 data,含 _table)
60
+ * @param {Object} options.config 模板配置(结构同 doPrint 的 config)
61
+ * @param {boolean} [options.isSomeSubtotalTr] 每页合计是否展示多个字段(对齐 doPrint 第三参,
62
+ * 影响合计行渲染与行高,进而影响分页边界)
63
+ * @returns {Promise<{pages: Array, remainPageHeight: number, tableCustomerRowHeight: number}>}
64
+ * pages: [[{type, index, begin, end}, ...], ...] 对 data._table[contents[i].dataKey] 行号的切片
65
+ * remainPageHeight: 最后一页剩余高度(供行数填充换算)
66
+ * tableCustomerRowHeight: 表格行高(供行数填充换算)
67
+ */
68
+ function computePages({ data, config, isSomeSubtotalTr }) {
69
+ // 不支持的模板类型提前告警(不阻断,返回结果交由调用方降级处理)
70
+ if (config.batchPrintConfig === 2 || config.page?.type === LONG_PRINT) {
71
+ console.warn(
72
+ '[gm-printer] computePages: 当前模板为合并打印或长条打印,computedPages 不生效,pages 将为空'
73
+ )
74
+ }
75
+
76
+ return new window.Promise(resolve => {
77
+ init()
78
+
79
+ const $app = $printer.contentWindow.document.getElementById('appContainer')
80
+ ReactDOM.unmountComponentAtNode($app)
81
+
82
+ // computedPages 处理采购明细拆行时会原地 splice data._table,
83
+ // 深拷贝一份,避免污染调用方数据
84
+ const clonedData = _.cloneDeep(data)
85
+ // computedPages 会往 config 顶层写 isSave(store.js),浅拷贝隔离开调用方的
86
+ // template 对象(stationv2 导出时同一 template 跨多个 sheet 复用)
87
+ const clonedConfig = { ...config }
88
+
89
+ // 依赖 WithStorePrinter 实例字段 printerStore(printer.js)读取分页结果,
90
+ // Printer 组件重构(store 挂载方式变化)时此处需同步
91
+ let printerRef = null
92
+ ReactDOM.render(
93
+ <Printer
94
+ ref={ref => {
95
+ printerRef = ref
96
+ }}
97
+ isSomeSubtotalTr={isSomeSubtotalTr}
98
+ config={clonedConfig}
99
+ data={clonedData}
100
+ onReady={() => {
101
+ // onReady 触发时 componentDidMount 已同步执行完 computedPages(),pages 就绪
102
+ // 模板含图片时等图片加载完成,避免图片撑高导致测量偏差
103
+ afterImgAndSvgLoaded(() => {
104
+ const store = printerRef.printerStore
105
+ const result = {
106
+ pages: toJS(store.pages),
107
+ remainPageHeight: store.remainPageHeight,
108
+ tableCustomerRowHeight: store.computedTableCustomerRowHeight
109
+ }
110
+
111
+ // 读取完成后卸载,避免 DOM 累积
112
+ ReactDOM.unmountComponentAtNode($app)
113
+ resolve(result)
114
+ }, $app)
115
+ }}
116
+ />,
117
+ $app
118
+ )
119
+ })
120
+ }
121
+
122
+ export { computePages }
@@ -7,6 +7,7 @@ import {
7
7
  doBatchFinancePrint,
8
8
  setPrintStyle
9
9
  } from './do_print'
10
+ import { computePages } from './compute_pages'
10
11
  import getCSS from './get_css'
11
12
 
12
13
  export {
@@ -17,5 +18,6 @@ export {
17
18
  getCSS,
18
19
  getPrintContainerHTML,
19
20
  doBatchFinancePrint,
20
- setPrintStyle
21
+ setPrintStyle,
22
+ computePages
21
23
  }