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/build/demo.js +9 -9
- package/package.json +1 -1
- package/src/common/data_key_util.js +22 -3
- package/src/common/editor_customize_config.js +3 -0
- package/src/common/editor_store.js +229 -28
- package/src/editor_purchase/editor_special_table.js +12 -6
- package/src/index.js +4 -2
- package/src/printer/compute_pages.js +122 -0
- package/src/printer/index.js +3 -1
- package/src/printer/multi_column.js +461 -0
- package/src/printer/printer.js +34 -15
- package/src/printer/store.js +304 -38
- package/src/printer/table.js +3 -7
- package/src/util.js +13 -10
package/package.json
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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 {
|
|
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
|
|
251
|
-
|
|
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
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
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
|
-
|
|
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
|
-
|
|
285
|
-
|
|
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 (
|
|
288
|
-
|
|
289
|
-
|
|
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
|
-
|
|
297
|
-
this.mockData._table[dataKey] =
|
|
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
|
-
//
|
|
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:
|
|
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
|
-
|
|
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
|
-
...
|
|
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
|
|
94
|
-
const
|
|
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
|
-
|
|
100
|
+
!curSuffix.startsWith('independent_rol_sku')
|
|
98
101
|
) {
|
|
99
|
-
editStore.setPurchaseTableKey(
|
|
102
|
+
editStore.setPurchaseTableKey(
|
|
103
|
+
buildDataKey(prefix, `independent_rol_sku${modifier}`),
|
|
104
|
+
addFields.specialTableConfig
|
|
105
|
+
)
|
|
100
106
|
} else if (
|
|
101
107
|
config.purchaseSettingKey === 'merchant' &&
|
|
102
|
-
|
|
108
|
+
!curSuffix.startsWith('independent_rol_address')
|
|
103
109
|
) {
|
|
104
110
|
editStore.setPurchaseTableKey(
|
|
105
|
-
|
|
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 }
|
package/src/printer/index.js
CHANGED
|
@@ -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
|
}
|