gm-printer 10.41.9 → 10.42.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/build/demo.js +9 -9
- package/package.json +1 -1
- package/src/common/editor_customize_config.js +3 -0
- package/src/common/editor_store.js +219 -23
- 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 +297 -33
- package/src/printer/table.js +3 -7
- package/src/util.js +13 -10
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
import { MULTI_SUFFIX, MULTI_SUFFIX3 } from '../config'
|
|
2
|
+
import { getMultiNumber, isMultiTable } from '../util'
|
|
3
|
+
|
|
4
|
+
export const INDEX_KEY = '序号'
|
|
5
|
+
|
|
6
|
+
const SPECIAL_ROW_KEYS = [
|
|
7
|
+
'_special',
|
|
8
|
+
'_diyCategorySubtotal',
|
|
9
|
+
'_diyTagSubtotal',
|
|
10
|
+
'_customTr'
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
export const getColSuffix = colIndex => {
|
|
14
|
+
if (colIndex <= 0) return ''
|
|
15
|
+
if (colIndex === 1) return MULTI_SUFFIX
|
|
16
|
+
return MULTI_SUFFIX3
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const getSerialKeys = colNumber =>
|
|
20
|
+
Array.from({ length: colNumber }, (_, c) => INDEX_KEY + getColSuffix(c))
|
|
21
|
+
|
|
22
|
+
/** 栏数:dataKey 的 multi/multi3,并用行上是否已有 _MULTI_SUFFIX 字段兜底 */
|
|
23
|
+
export const detectColNumber = (dataKey, rows) => {
|
|
24
|
+
let n = isMultiTable(dataKey) ? getMultiNumber(dataKey) : 1
|
|
25
|
+
const sample = (rows || []).find(
|
|
26
|
+
row => row && !row._isEmptyData && !isSpecialTableRow(row)
|
|
27
|
+
)
|
|
28
|
+
if (sample) {
|
|
29
|
+
const keys = Object.keys(sample)
|
|
30
|
+
if (keys.some(key => key.endsWith(MULTI_SUFFIX3))) {
|
|
31
|
+
n = Math.max(n, 3)
|
|
32
|
+
} else if (keys.some(key => key.endsWith(MULTI_SUFFIX))) {
|
|
33
|
+
n = Math.max(n, 2)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return n
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const isSpecialTableRow = row =>
|
|
40
|
+
!!(row && SPECIAL_ROW_KEYS.some(key => row[key]))
|
|
41
|
+
|
|
42
|
+
export const isFilledIndexValue = value => value !== '' && value != null
|
|
43
|
+
|
|
44
|
+
export const toSerialNumber = value => {
|
|
45
|
+
if (!isFilledIndexValue(value)) return null
|
|
46
|
+
const n = Number(value)
|
|
47
|
+
return Number.isFinite(n) ? n : null
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const splitKeyCol = key => {
|
|
51
|
+
if (key.endsWith(MULTI_SUFFIX3)) {
|
|
52
|
+
return { col: 2, base: key.slice(0, -MULTI_SUFFIX3.length) }
|
|
53
|
+
}
|
|
54
|
+
if (key.endsWith(MULTI_SUFFIX)) {
|
|
55
|
+
return { col: 1, base: key.slice(0, -MULTI_SUFFIX.length) }
|
|
56
|
+
}
|
|
57
|
+
return { col: 0, base: key }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** 把多栏打包行拆成各列的原始行数据 */
|
|
61
|
+
export const splitPackedRow = (row, colNumber) => {
|
|
62
|
+
const cols = Array.from({ length: colNumber }, () => ({}))
|
|
63
|
+
Object.keys(row || {}).forEach(key => {
|
|
64
|
+
if (key === '_isEmptyData') return
|
|
65
|
+
const { col, base } = splitKeyCol(key)
|
|
66
|
+
if (col < colNumber) {
|
|
67
|
+
cols[col][base] = row[key]
|
|
68
|
+
}
|
|
69
|
+
})
|
|
70
|
+
return cols
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const isRealItem = item => isFilledIndexValue(item?.[INDEX_KEY])
|
|
74
|
+
|
|
75
|
+
/** 去掉多栏后缀字段,避免旧右列残留被 assign 进新行 */
|
|
76
|
+
export const stripMultiSuffixFields = item => {
|
|
77
|
+
if (!item || typeof item !== 'object') return item
|
|
78
|
+
const next = {}
|
|
79
|
+
Object.keys(item).forEach(key => {
|
|
80
|
+
if (key === '_isEmptyData') return
|
|
81
|
+
if (key.endsWith(MULTI_SUFFIX) || key.endsWith(MULTI_SUFFIX3)) return
|
|
82
|
+
next[key] = item[key]
|
|
83
|
+
})
|
|
84
|
+
return next
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** 横向多栏:每行从左到右拆出商品,得到原始顺序 */
|
|
88
|
+
export const flattenHorizontalPackedRows = (rows, colNumber) => {
|
|
89
|
+
const items = []
|
|
90
|
+
const cols = Math.max(colNumber || 1, 1)
|
|
91
|
+
;(rows || []).forEach(row => {
|
|
92
|
+
if (!row || row._isEmptyData || isSpecialTableRow(row)) return
|
|
93
|
+
if (cols <= 1) {
|
|
94
|
+
items.push(stripMultiSuffixFields(row))
|
|
95
|
+
return
|
|
96
|
+
}
|
|
97
|
+
splitPackedRow(row, cols).forEach(item => {
|
|
98
|
+
if (isRealItem(item)) items.push(stripMultiSuffixFields(item))
|
|
99
|
+
})
|
|
100
|
+
})
|
|
101
|
+
return items
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** 纵向整单打包:左列全部 + 右列全部,还原商品原始顺序 */
|
|
105
|
+
export const flattenVerticalSegment = (rows, colNumber) => {
|
|
106
|
+
const columns = Array.from({ length: colNumber }, () => [])
|
|
107
|
+
rows.forEach(row => {
|
|
108
|
+
if (isSpecialTableRow(row)) return
|
|
109
|
+
splitPackedRow(row, colNumber).forEach((item, col) => {
|
|
110
|
+
if (isRealItem(item)) {
|
|
111
|
+
columns[col].push(stripMultiSuffixFields(item))
|
|
112
|
+
}
|
|
113
|
+
})
|
|
114
|
+
})
|
|
115
|
+
return columns.reduce((list, col) => list.concat(col), [])
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const assignItemToRow = (row, item, suffix) => {
|
|
119
|
+
const clean = stripMultiSuffixFields(item) || {}
|
|
120
|
+
Object.keys(clean).forEach(key => {
|
|
121
|
+
row[key + suffix] = clean[key]
|
|
122
|
+
})
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* 按「先填满当前页左列,再填右列」打包。
|
|
127
|
+
* row r 的第 c 列对应 items[c * rowCount + r]
|
|
128
|
+
* 空单元格补序号也按列优先(先下后右),与商品落位一致。
|
|
129
|
+
* @param {object} [options]
|
|
130
|
+
* @param {number} [options.serialStart] 填充序号的起始值(含)
|
|
131
|
+
*/
|
|
132
|
+
export const packVerticalNewspaper = (
|
|
133
|
+
items,
|
|
134
|
+
colNumber,
|
|
135
|
+
rowCount,
|
|
136
|
+
fillIndex = false,
|
|
137
|
+
options = {}
|
|
138
|
+
) => {
|
|
139
|
+
const { serialStart } = options
|
|
140
|
+
const rows = Array.from({ length: rowCount }, () => ({}))
|
|
141
|
+
let maxSerial = 0
|
|
142
|
+
;(items || []).forEach(item => {
|
|
143
|
+
const n = toSerialNumber(item?.[INDEX_KEY])
|
|
144
|
+
if (n != null && n > maxSerial) maxSerial = n
|
|
145
|
+
})
|
|
146
|
+
if (serialStart != null && serialStart - 1 > maxSerial) {
|
|
147
|
+
maxSerial = serialStart - 1
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
for (let c = 0; c < colNumber; c++) {
|
|
151
|
+
for (let r = 0; r < rowCount; r++) {
|
|
152
|
+
const item = (items || [])[c * rowCount + r]
|
|
153
|
+
if (!isRealItem(item)) continue
|
|
154
|
+
assignItemToRow(rows[r], item, getColSuffix(c))
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (fillIndex) {
|
|
159
|
+
let nextSerial = maxSerial
|
|
160
|
+
for (let c = 0; c < colNumber; c++) {
|
|
161
|
+
for (let r = 0; r < rowCount; r++) {
|
|
162
|
+
const key = INDEX_KEY + getColSuffix(c)
|
|
163
|
+
if (isFilledIndexValue(rows[r][key])) {
|
|
164
|
+
const n = toSerialNumber(rows[r][key])
|
|
165
|
+
if (n != null && n > nextSerial) nextSerial = n
|
|
166
|
+
continue
|
|
167
|
+
}
|
|
168
|
+
rows[r][key] = ++nextSerial
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
} else {
|
|
172
|
+
for (let c = 0; c < colNumber; c++) {
|
|
173
|
+
for (let r = 0; r < rowCount; r++) {
|
|
174
|
+
const key = INDEX_KEY + getColSuffix(c)
|
|
175
|
+
if (!isFilledIndexValue(rows[r][key])) {
|
|
176
|
+
rows[r][key] = ''
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
rows.forEach((row, r) => {
|
|
183
|
+
let hasRealItem = false
|
|
184
|
+
for (let c = 0; c < colNumber; c++) {
|
|
185
|
+
if (isRealItem((items || [])[c * rowCount + r])) {
|
|
186
|
+
hasRealItem = true
|
|
187
|
+
break
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
if (!hasRealItem) {
|
|
191
|
+
row._isEmptyData = true
|
|
192
|
+
}
|
|
193
|
+
})
|
|
194
|
+
return rows
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* 横向多栏:每行从左到右落位(row r 的第 c 列 ← items[r * colNumber + c])
|
|
199
|
+
* 用于缺省 multi/multi3 表时从单栏商品生成打包数据。
|
|
200
|
+
*/
|
|
201
|
+
export const packHorizontalRowMajor = (items, colNumber) => {
|
|
202
|
+
const cols = Math.max(colNumber || 1, 1)
|
|
203
|
+
const list = items || []
|
|
204
|
+
const rowCount = list.length ? Math.ceil(list.length / cols) : 0
|
|
205
|
+
const rows = Array.from({ length: rowCount }, () => ({}))
|
|
206
|
+
for (let r = 0; r < rowCount; r++) {
|
|
207
|
+
for (let c = 0; c < cols; c++) {
|
|
208
|
+
const item = list[r * cols + c]
|
|
209
|
+
if (!isRealItem(item)) continue
|
|
210
|
+
assignItemToRow(rows[r], item, getColSuffix(c))
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return rows
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export const getMaxSerial = (rows, colNumber) => {
|
|
217
|
+
let max = 0
|
|
218
|
+
const keys = getSerialKeys(colNumber)
|
|
219
|
+
;(rows || []).forEach(row => {
|
|
220
|
+
keys.forEach(key => {
|
|
221
|
+
const n = toSerialNumber(row?.[key])
|
|
222
|
+
if (n != null && n > max) max = n
|
|
223
|
+
})
|
|
224
|
+
})
|
|
225
|
+
return max
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/** 空白行补齐每一栏的序号 */
|
|
229
|
+
export const fillEmptyRowIndex = (
|
|
230
|
+
item,
|
|
231
|
+
index,
|
|
232
|
+
lastKey,
|
|
233
|
+
colNumber,
|
|
234
|
+
fillIndex
|
|
235
|
+
) => {
|
|
236
|
+
const next = { ...item }
|
|
237
|
+
getSerialKeys(colNumber).forEach((key, col) => {
|
|
238
|
+
next[key] = fillIndex ? lastKey + index * colNumber + col : ''
|
|
239
|
+
})
|
|
240
|
+
return next
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* 按样板行生成空行骨架(拷贝字段为 ''),避免多栏模板读 undefined 抛错。
|
|
245
|
+
* 并预留各栏序号字段。
|
|
246
|
+
*/
|
|
247
|
+
export const createEmptyRowSkeleton = (sampleRow, colNumber = 1) => {
|
|
248
|
+
const skeleton = { _isEmptyData: true }
|
|
249
|
+
if (sampleRow && typeof sampleRow === 'object') {
|
|
250
|
+
Object.keys(sampleRow).forEach(key => {
|
|
251
|
+
if (key === '_isEmptyData') return
|
|
252
|
+
skeleton[key] = ''
|
|
253
|
+
})
|
|
254
|
+
}
|
|
255
|
+
const cols = Math.max(colNumber || 1, 1)
|
|
256
|
+
getSerialKeys(cols).forEach(key => {
|
|
257
|
+
if (skeleton[key] === undefined) skeleton[key] = ''
|
|
258
|
+
})
|
|
259
|
+
return skeleton
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export const createEmptyRows = (count, sampleRow, colNumber = 1) => {
|
|
263
|
+
if (count <= 0) return []
|
|
264
|
+
const skeleton = createEmptyRowSkeleton(sampleRow, colNumber)
|
|
265
|
+
return Array.from({ length: count }, () => ({ ...skeleton }))
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* 纵向多栏空行数:
|
|
270
|
+
* - 仍有 _isEmptyData 时:用「总行 - 对半商品行」钉死(开关填充序号,避免 repack 吃空行)
|
|
271
|
+
* - 已无空行标记时:走 fallback(remain/行高),供多栏/排列切换清空后重补
|
|
272
|
+
* (left-fill 会留下无 _isEmptyData 的多余商品行,不能当总行钉死)
|
|
273
|
+
*/
|
|
274
|
+
export const resolveVerticalEmptyCount = (
|
|
275
|
+
tableLength,
|
|
276
|
+
baseRowCount,
|
|
277
|
+
fallbackEmptyCount = 0,
|
|
278
|
+
hasEmptyMarked = false
|
|
279
|
+
) => {
|
|
280
|
+
const base = Math.max(0, baseRowCount || 0)
|
|
281
|
+
const pinned = Math.max(0, (tableLength || 0) - base)
|
|
282
|
+
if (hasEmptyMarked && pinned > 0) return pinned
|
|
283
|
+
return Math.max(0, fallbackEmptyCount || 0)
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* 渲染/插值前补齐当前行每一栏序号。
|
|
288
|
+
* 纵向表(_vertical)序号由 repack 统一生成,这里不再按横向公式补,避免断号/残留错觉。
|
|
289
|
+
*/
|
|
290
|
+
export const ensureRowSerialKeys = (
|
|
291
|
+
list,
|
|
292
|
+
index,
|
|
293
|
+
colNumber,
|
|
294
|
+
fillIndex,
|
|
295
|
+
dataKey
|
|
296
|
+
) => {
|
|
297
|
+
const row = list?.[index]
|
|
298
|
+
if (!row || !fillIndex || colNumber < 1 || isSpecialTableRow(row)) {
|
|
299
|
+
return row
|
|
300
|
+
}
|
|
301
|
+
// 纵向多栏:信任 repack 结果
|
|
302
|
+
if (dataKey && /_vertical$/.test(dataKey)) {
|
|
303
|
+
return row
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const keys = getSerialKeys(colNumber)
|
|
307
|
+
const missing = keys.filter(key => !isFilledIndexValue(row[key]))
|
|
308
|
+
if (!missing.length) return row
|
|
309
|
+
|
|
310
|
+
const realRows = (list || []).filter(
|
|
311
|
+
item => item && !item._isEmptyData && !isSpecialTableRow(item)
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
if (row._isEmptyData) {
|
|
315
|
+
let max = getMaxSerial(realRows, colNumber)
|
|
316
|
+
const lastReal = realRows[realRows.length - 1]
|
|
317
|
+
if (lastReal) {
|
|
318
|
+
keys.forEach(key => {
|
|
319
|
+
if (!isFilledIndexValue(lastReal[key])) max += 1
|
|
320
|
+
})
|
|
321
|
+
}
|
|
322
|
+
const emptyIndex = (list || [])
|
|
323
|
+
.slice(0, index)
|
|
324
|
+
.filter(item => item?._isEmptyData).length
|
|
325
|
+
const next = { ...row }
|
|
326
|
+
keys.forEach((key, col) => {
|
|
327
|
+
next[key] = max + 1 + emptyIndex * colNumber + col
|
|
328
|
+
})
|
|
329
|
+
return next
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const lastRealIndex = (list || []).reduce((acc, item, i) => {
|
|
333
|
+
if (item && !item._isEmptyData && !isSpecialTableRow(item)) return i
|
|
334
|
+
return acc
|
|
335
|
+
}, -1)
|
|
336
|
+
if (index !== lastRealIndex) return row
|
|
337
|
+
|
|
338
|
+
let max = getMaxSerial(realRows, colNumber)
|
|
339
|
+
const next = { ...row }
|
|
340
|
+
keys.forEach(key => {
|
|
341
|
+
if (!isFilledIndexValue(next[key])) {
|
|
342
|
+
next[key] = ++max
|
|
343
|
+
}
|
|
344
|
+
})
|
|
345
|
+
return next
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/** 最后一行右侧空单元格续编序号 */
|
|
349
|
+
export const fillPartialLastRow = (rows, colNumber, fillIndex) => {
|
|
350
|
+
if (!fillIndex || !rows?.length || colNumber <= 1) return rows
|
|
351
|
+
const lastIndex = rows.length - 1
|
|
352
|
+
const last = rows[lastIndex]
|
|
353
|
+
if (!last || last._isEmptyData || isSpecialTableRow(last)) return rows
|
|
354
|
+
|
|
355
|
+
let max = getMaxSerial(rows, colNumber)
|
|
356
|
+
const next = { ...last }
|
|
357
|
+
let changed = false
|
|
358
|
+
getSerialKeys(colNumber).forEach(key => {
|
|
359
|
+
if (!isFilledIndexValue(next[key])) {
|
|
360
|
+
next[key] = ++max
|
|
361
|
+
changed = true
|
|
362
|
+
}
|
|
363
|
+
})
|
|
364
|
+
if (!changed) return rows
|
|
365
|
+
const result = rows.slice()
|
|
366
|
+
result[lastIndex] = next
|
|
367
|
+
return result
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/** 整段按列优先重编序号(先左列上→下,再右列),数据行与空行连续不断号 */
|
|
371
|
+
export const renumberSegmentSerialsColumnMajor = (
|
|
372
|
+
rows,
|
|
373
|
+
begin,
|
|
374
|
+
end,
|
|
375
|
+
colNumber,
|
|
376
|
+
startSerial = 1
|
|
377
|
+
) => {
|
|
378
|
+
let next = startSerial
|
|
379
|
+
for (let c = 0; c < colNumber; c++) {
|
|
380
|
+
for (let r = begin; r < end; r++) {
|
|
381
|
+
const row = rows[r]
|
|
382
|
+
if (!row || isSpecialTableRow(row)) continue
|
|
383
|
+
const key = INDEX_KEY + getColSuffix(c)
|
|
384
|
+
rows[r] = { ...row, [key]: next++ }
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
return next
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* 按分页区间把纵向多栏数据改成「每页先左后右」。
|
|
392
|
+
* - 商品按序号灌满当前段左列再排右列(含空行的整段行数)
|
|
393
|
+
* - 每一行都是新建对象,清除旧右列残留
|
|
394
|
+
* - fillIndex 时整段按列重编序号,有数据/无数据同一套规则
|
|
395
|
+
*/
|
|
396
|
+
export const repackVerticalTableByPageRanges = (
|
|
397
|
+
rows,
|
|
398
|
+
colNumber,
|
|
399
|
+
pageRanges,
|
|
400
|
+
fillIndex = false,
|
|
401
|
+
sourceItems
|
|
402
|
+
) => {
|
|
403
|
+
if (!rows?.length || !pageRanges?.length || colNumber < 2) return rows
|
|
404
|
+
const items = (sourceItems && sourceItems.length
|
|
405
|
+
? sourceItems
|
|
406
|
+
: flattenVerticalSegment(rows, colNumber)
|
|
407
|
+
).map(stripMultiSuffixFields)
|
|
408
|
+
|
|
409
|
+
if (!items.length && !fillIndex) return rows
|
|
410
|
+
|
|
411
|
+
// 全新数组,避免与旧行对象共享引用
|
|
412
|
+
const result = new Array(rows.length)
|
|
413
|
+
for (let k = 0; k < rows.length; k++) {
|
|
414
|
+
result[k] = rows[k]
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
let consumed = 0
|
|
418
|
+
let nextSerial = 1
|
|
419
|
+
pageRanges.forEach(({ begin, end }) => {
|
|
420
|
+
const packEnd = Math.min(end, rows.length)
|
|
421
|
+
let i = begin
|
|
422
|
+
while (i < packEnd) {
|
|
423
|
+
if (isSpecialTableRow(rows[i])) {
|
|
424
|
+
result[i] = rows[i]
|
|
425
|
+
i++
|
|
426
|
+
continue
|
|
427
|
+
}
|
|
428
|
+
let j = i
|
|
429
|
+
while (j < packEnd && !isSpecialTableRow(rows[j])) {
|
|
430
|
+
j++
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
const rowCount = j - i
|
|
434
|
+
if (rowCount <= 0) {
|
|
435
|
+
i = j
|
|
436
|
+
continue
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
const capacity = rowCount * colNumber
|
|
440
|
+
const chunk = items.slice(consumed, consumed + capacity)
|
|
441
|
+
const packed = packVerticalNewspaper(chunk, colNumber, rowCount, false)
|
|
442
|
+
packed.forEach((row, offset) => {
|
|
443
|
+
result[i + offset] = row
|
|
444
|
+
})
|
|
445
|
+
consumed += chunk.length
|
|
446
|
+
|
|
447
|
+
if (fillIndex) {
|
|
448
|
+
nextSerial = renumberSegmentSerialsColumnMajor(
|
|
449
|
+
result,
|
|
450
|
+
i,
|
|
451
|
+
j,
|
|
452
|
+
colNumber,
|
|
453
|
+
nextSerial
|
|
454
|
+
)
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
i = j
|
|
458
|
+
}
|
|
459
|
+
})
|
|
460
|
+
return result
|
|
461
|
+
}
|
package/src/printer/printer.js
CHANGED
|
@@ -11,7 +11,7 @@ import Table from './table'
|
|
|
11
11
|
import LongPrintTable from './long_print_table'
|
|
12
12
|
import MergePage from './merge_page'
|
|
13
13
|
import { LONG_PRINT } from '../config'
|
|
14
|
-
import
|
|
14
|
+
import { getDataKey } from '../util'
|
|
15
15
|
|
|
16
16
|
/** 当前 panel 是否为该表格 content 在全部分页中的最后一次出现 */
|
|
17
17
|
const isLastTablePanel = (pages, pageIndex, panelIndexInPage, tableIndex) => {
|
|
@@ -119,6 +119,8 @@ class Printer extends React.Component {
|
|
|
119
119
|
const batchPrintConfig = config.batchPrintConfig
|
|
120
120
|
// didMount 代表第一次渲染完成,先分页再 ready,避免空 pages 触发 renderPage
|
|
121
121
|
if (batchPrintConfig !== 2) {
|
|
122
|
+
// 先分页拿到剩余高度,再补空行,最后按补空后的数据重新分页
|
|
123
|
+
// (若先分页后补空却不重算,panel.end 仍是补空前行数,空行/后半数据会被裁掉)
|
|
122
124
|
printerStore.computedPages()
|
|
123
125
|
if (config.autoFillConfig?.checked) {
|
|
124
126
|
this.props.printerStore.setAutofillConfig(
|
|
@@ -128,6 +130,7 @@ class Printer extends React.Component {
|
|
|
128
130
|
config.autoFillConfig?.fillIndex || false
|
|
129
131
|
)
|
|
130
132
|
printerStore.changeTableData()
|
|
133
|
+
printerStore.computedPages()
|
|
131
134
|
}
|
|
132
135
|
// 获取剩余空白高度,传到editor
|
|
133
136
|
getremainpageHeight && getremainpageHeight(printerStore.remainPageHeight)
|
|
@@ -158,7 +161,10 @@ class Printer extends React.Component {
|
|
|
158
161
|
switch (content.type) {
|
|
159
162
|
case 'table':
|
|
160
163
|
// eslint-disable-next-line no-case-declarations
|
|
161
|
-
const list =
|
|
164
|
+
const list =
|
|
165
|
+
printerStore.data._table[
|
|
166
|
+
getDataKey(content.dataKey, content.arrange)
|
|
167
|
+
] || printerStore.data._table[content.dataKey]
|
|
162
168
|
return (
|
|
163
169
|
<Table
|
|
164
170
|
key={`contents.table.${index}`}
|
|
@@ -245,7 +251,9 @@ class Printer extends React.Component {
|
|
|
245
251
|
|
|
246
252
|
renderPage() {
|
|
247
253
|
const { printerStore, isSomeSubtotalTr } = this.props
|
|
248
|
-
const { config,
|
|
254
|
+
const { config, isAutoFilling } = printerStore
|
|
255
|
+
// 订阅 dataRevision,保证 _table 重排后预览会刷新
|
|
256
|
+
const dataRevision = printerStore.dataRevision
|
|
249
257
|
return (
|
|
250
258
|
<>
|
|
251
259
|
{_.map(printerStore.pages, (page, i) => {
|
|
@@ -254,29 +262,40 @@ class Printer extends React.Component {
|
|
|
254
262
|
return null
|
|
255
263
|
}
|
|
256
264
|
return (
|
|
257
|
-
<Page key={i}>
|
|
265
|
+
<Page key={`${i}-${dataRevision}`}>
|
|
258
266
|
<Header config={config.header} pageIndex={i} />
|
|
259
267
|
{_.map(page, (panel, ii) => {
|
|
260
268
|
const content = config.contents[panel.index]
|
|
261
269
|
const autoFillConfig = config?.autoFillConfig || {}
|
|
262
|
-
const
|
|
270
|
+
const resolvedDataKey = getDataKey(
|
|
271
|
+
content?.dataKey,
|
|
272
|
+
content?.arrange
|
|
273
|
+
)
|
|
274
|
+
const tableRows =
|
|
275
|
+
printerStore.data._table?.[resolvedDataKey] ||
|
|
276
|
+
printerStore.data._table?.[content?.dataKey] ||
|
|
277
|
+
[]
|
|
278
|
+
const isAutofillTable =
|
|
263
279
|
isLastPage &&
|
|
264
280
|
isAutoFilling &&
|
|
265
|
-
panel.end &&
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
281
|
+
panel.end != null &&
|
|
282
|
+
panel.type === 'table' &&
|
|
283
|
+
(content?.dataKey === autoFillConfig?.dataKey ||
|
|
284
|
+
content?.dataKey === printerStore.tableConfig?.dataKey ||
|
|
285
|
+
autoFillConfig?.region === `contents.table.${panel.index}`)
|
|
286
|
+
// 空行必须已写入 _table(数据驱动);渲染不得用剩余高度「虚造」行,
|
|
287
|
+
// 否则 list[i] 为 undefined → 模板抛错露出 {{列.xxx_MULTI_SUFFIX}}
|
|
288
|
+
let end = panel.end
|
|
289
|
+
if (isAutofillTable) {
|
|
290
|
+
end = Math.max(panel.end || 0, tableRows.length)
|
|
291
|
+
}
|
|
292
|
+
end = Math.min(end || 0, tableRows.length)
|
|
274
293
|
|
|
275
294
|
switch (panel.type) {
|
|
276
295
|
case 'table':
|
|
277
296
|
return (
|
|
278
297
|
<Table
|
|
279
|
-
key={`contents.table.${panel.index}.${ii}`}
|
|
298
|
+
key={`contents.table.${panel.index}.${ii}.${dataRevision}`}
|
|
280
299
|
name={`contents.table.${panel.index}`}
|
|
281
300
|
config={content}
|
|
282
301
|
range={{
|