gm-printer 10.39.4-beta.2 → 10.41.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.
@@ -12,54 +12,66 @@ const OverallOrder = props => {
12
12
  printerStore,
13
13
  printerStore: {
14
14
  data: { common }
15
- }
15
+ },
16
+ isLastTablePage
16
17
  } = props
17
18
 
18
- return (
19
+ // showEachPage 默认 true(老模板缺省视为每页展示);false 时仅表格最后一页渲染
20
+ const showEachPage = overallOrder?.showEachPage !== false
21
+ const shouldShow =
19
22
  overallOrder?.show &&
20
- printerStore?.ready && (
21
- <tr>
22
- {_.map(overallOrder.fields, (item, index) => {
23
- return (
24
- <td colSpan={item.colSpan} key={index}>
25
- <div style={{ ...item.style }} className='gm-flex-page'>
26
- {item.name}
27
- <div
28
- className={classNames('gm-flex-page', {
29
- 'gm-flex-justify-between-page':
30
- overallOrder?.isUpperLowerCaseSeparate,
31
- 'gm-flex-grow-page': overallOrder?.isUpperLowerCaseSeparate
32
- })}
23
+ printerStore?.ready &&
24
+ (showEachPage || isLastTablePage)
25
+
26
+ if (!shouldShow) return null
27
+
28
+ return (
29
+ <tr>
30
+ {_.map(overallOrder.fields, (item, index) => {
31
+ return (
32
+ <td colSpan={item.colSpan} key={index}>
33
+ <div style={{ ...item.style }} className='gm-flex-page'>
34
+ {item.name}
35
+ <div
36
+ className={classNames('gm-flex-page', {
37
+ 'gm-flex-justify-between-page':
38
+ overallOrder?.isUpperLowerCaseSeparate,
39
+ 'gm-flex-grow-page': overallOrder?.isUpperLowerCaseSeparate
40
+ })}
41
+ >
42
+ <span
43
+ className={
44
+ overallOrder?.isUpperCaseBefore
45
+ ? 'gm-printer-subtotal-isUpperCaseBefore-inter'
46
+ : ''
47
+ }
33
48
  >
34
- <span
35
- className={
36
- overallOrder?.isUpperCaseBefore
37
- ? 'gm-printer-subtotal-isUpperCaseBefore-inter'
38
- : ''
39
- }
40
- >
41
- {common?.[item.valueField] ?? ''}
49
+ {common?.[item.valueField] ?? ''}
50
+ </span>
51
+ {overallOrder?.needUpperCase && (
52
+ <span>
53
+ {common?.[item.valueField]
54
+ ? coverDigit2Uppercase(common[item.valueField])
55
+ : ''}
42
56
  </span>
43
- {overallOrder?.needUpperCase && (
44
- <span>
45
- {common?.[item.valueField]
46
- ? coverDigit2Uppercase(common[item.valueField])
47
- : ''}
48
- </span>
49
- )}
50
- </div>
57
+ )}
51
58
  </div>
52
- </td>
53
- )
54
- })}
55
- </tr>
56
- )
59
+ </div>
60
+ </td>
61
+ )
62
+ })}
63
+ </tr>
57
64
  )
58
65
  }
59
66
  OverallOrder.propTypes = {
60
67
  config: PropTypes.object.isRequired,
61
68
  range: PropTypes.object.isRequired,
62
- printerStore: PropTypes.object
69
+ printerStore: PropTypes.object,
70
+ isLastTablePage: PropTypes.bool
71
+ }
72
+
73
+ OverallOrder.defaultProps = {
74
+ isLastTablePage: true
63
75
  }
64
76
 
65
77
  export default observer(OverallOrder)
package/src/util.js CHANGED
@@ -2,23 +2,19 @@ import _ from 'lodash'
2
2
  import Big from 'big.js'
3
3
 
4
4
  function getHeight(el) {
5
- // 打印渲染在 iframe 内时,必须用元素所属 document 的 window,
6
- // 否则 parent window.getComputedStyle 可能得到错误值、测高为 0
7
- const view = (el.ownerDocument && el.ownerDocument.defaultView) || window
8
- const styles = view.getComputedStyle(el)
5
+ const styles = window.getComputedStyle(el)
9
6
  const height = el.offsetHeight
10
- const borderTopWidth = parseFloat(styles.borderTopWidth) || 0
11
- const borderBottomWidth = parseFloat(styles.borderBottomWidth) || 0
12
- const paddingTop = parseFloat(styles.paddingTop) || 0
13
- const paddingBottom = parseFloat(styles.paddingBottom) || 0
7
+ const borderTopWidth = parseFloat(styles.borderTopWidth)
8
+ const borderBottomWidth = parseFloat(styles.borderBottomWidth)
9
+ const paddingTop = parseFloat(styles.paddingTop)
10
+ const paddingBottom = parseFloat(styles.paddingBottom)
14
11
  return (
15
12
  height - borderBottomWidth - borderTopWidth - paddingTop - paddingBottom
16
13
  )
17
14
  }
18
15
 
19
16
  function getWidth(el) {
20
- const view = (el.ownerDocument && el.ownerDocument.defaultView) || window
21
- const styles = view.getComputedStyle(el)
17
+ const styles = window.getComputedStyle(el)
22
18
  // 取width保留小数点用 getBoundClientRect
23
19
  const style = el.getBoundingClientRect()
24
20
  const width = Math.round(style.width * 100) / 100 // 保留两位小数并四舍五入
@@ -1,461 +0,0 @@
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
- }