gm-printer 10.39.4-beta.2 → 10.39.4-beta.21

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.
Files changed (37) hide show
  1. package/build/demo.js +9 -9
  2. package/locales/en.json +3 -0
  3. package/locales/th.json +3 -0
  4. package/locales/zh-HK.json +3 -0
  5. package/package.json +1 -1
  6. package/src/common/common_context_menu.js +6 -4
  7. package/src/common/component.js +64 -8
  8. package/src/common/data_key_util.js +35 -0
  9. package/src/common/editor_edit_field.js +288 -167
  10. package/src/common/editor_store.js +230 -42
  11. package/src/common/hoc_with_store.js +9 -0
  12. package/src/common/style.lesss +43 -0
  13. package/src/common/util.js +58 -4
  14. package/src/editor/context_menu.js +17 -0
  15. package/src/editor/store.js +13 -0
  16. package/src/editor_purchase/editor.js +2 -1
  17. package/src/editor_purchase/editor_special_table.js +39 -17
  18. package/src/editor_purchase/editor_sum_table.js +5 -1
  19. package/src/editor_purchase/store.js +48 -45
  20. package/src/finance_voucher/components/table_subtotal_tr.js +12 -18
  21. package/src/index.js +6 -2
  22. package/src/printer/block.js +154 -4
  23. package/src/printer/compute_pages.js +122 -0
  24. package/src/printer/index.js +3 -1
  25. package/src/printer/long_print_table/index.js +14 -4
  26. package/src/printer/multi_column.js +1 -0
  27. package/src/printer/page_summary.js +3 -1
  28. package/src/printer/printer.js +23 -3
  29. package/src/printer/store.js +141 -91
  30. package/src/printer/table.js +11 -3
  31. package/src/printer/table_diy_overallOrder_tr.js +8 -15
  32. package/src/printer/table_diy_summary_tr.js +18 -35
  33. package/src/printer/table_overallOrder_tr.js +49 -37
  34. package/src/printer/table_special_tr.js +17 -2
  35. package/src/printer/table_subtotal_tr.js +100 -16
  36. package/src/util.js +34 -8
  37. package/yarn-error.log +372 -152
@@ -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)
@@ -25,6 +25,21 @@ const SpecialTr = ({ data, config }) => {
25
25
  } = specialConfig
26
26
  const { list, type, fixedSize } = data
27
27
 
28
+ // 「显示分类名称/小计文案修改」实时生效:数据行带 _prefix 原料时,按当前 specialConfig 重拼前缀
29
+ // 打印时配置与数据层拼前缀所用配置一致,重拼结果不变;编辑器预览改配置时实时刷新;导出(paint.js)不走本组件不受影响
30
+ const applySubtotalText = html => {
31
+ if (typeof html !== 'string' || data._prefix === undefined) {
32
+ return html
33
+ }
34
+ const showCategoryName = specialConfig?.showCategoryName ?? true
35
+ const subtotalText = specialConfig?.subtotalText ?? '小计'
36
+ const newPrefix = showCategoryName
37
+ ? `${data._categoryName ?? ''}${subtotalText}:`
38
+ : `${subtotalText}:`
39
+ // 前缀位于字符串头部,首次命中即替换;用函数替换避免 newPrefix 含 $ 序列时被特俗解释
40
+ return html.replace(data._prefix, () => newPrefix)
41
+ }
42
+
28
43
  let compiled
29
44
  try {
30
45
  compiled = _.template(template_text, { interpolate: /{{([\s\S]+?)}}/g })
@@ -119,7 +134,7 @@ const SpecialTr = ({ data, config }) => {
119
134
  className='gm-flex-page gm-flex-justify-between-page'
120
135
  style={{ 'justify-content': 'space-between' }}
121
136
  dangerouslySetInnerHTML={{
122
- __html: htmlStr || data.text
137
+ __html: applySubtotalText(htmlStr || data.text)
123
138
  }}
124
139
  />
125
140
  </td>
@@ -145,7 +160,7 @@ const SpecialTr = ({ data, config }) => {
145
160
  colSpan={99}
146
161
  style={Object.assign({ fontWeight: 'bold' }, style)}
147
162
  dangerouslySetInnerHTML={{
148
- __html: getHtml() || data.text
163
+ __html: applySubtotalText(getHtml() || data.text)
149
164
  }}
150
165
  />
151
166
  </tr>
@@ -4,7 +4,7 @@ import PropTypes from 'prop-types'
4
4
  import _ from 'lodash'
5
5
  import { MULTI_SUFFIX, MULTI_SUFFIX3 } from '../config'
6
6
  import Big from 'big.js'
7
- import { coverDigit2Uppercase, getDataKey } from '../util'
7
+ import { coverDigit2Uppercase, getDataKey, maskedSum } from '../util'
8
8
  import { observer } from 'mobx-react'
9
9
  import { get } from 'mobx'
10
10
  import classNames from 'classnames'
@@ -76,21 +76,18 @@ const SubtotalTr = props => {
76
76
  if (field === 'real_item_price') field = '出库金额'
77
77
  if (field === 'settle_money2') field = '结算金额'
78
78
  if (field === 'total_money2') field = '金额'
79
- return _.reduce(
80
- list,
81
- (a, b) => {
82
- let result = a
83
- result = a.plus(b[field] || 0)
84
- if (b?.[field + MULTI_SUFFIX]) {
85
- result = result.plus(b?.[field + MULTI_SUFFIX])
86
- }
87
- if (b?.[field + MULTI_SUFFIX3]) {
88
- result = result.plus(b?.[field + MULTI_SUFFIX3])
89
- }
90
- return result
91
- },
92
- Big(0)
93
- ).toFixed(2)
79
+ // 收集本列所有值(含多栏后缀列),任一脱敏 '***' 则合计显 '***',否则 Big('***') 会崩溃
80
+ const values = []
81
+ _.each(list, b => {
82
+ values.push(b[field])
83
+ if (b?.[field + MULTI_SUFFIX]) {
84
+ values.push(b[field + MULTI_SUFFIX])
85
+ }
86
+ if (b?.[field + MULTI_SUFFIX3]) {
87
+ values.push(b[field + MULTI_SUFFIX3])
88
+ }
89
+ })
90
+ return maskedSum(values)
94
91
  }
95
92
 
96
93
  const getData = field => {
@@ -134,6 +131,93 @@ const SubtotalTr = props => {
134
131
  </tr>
135
132
  )
136
133
  } else {
134
+ // 单元格拆分展示:第一格(左侧文案+数值)拆分为两个单元格,对齐复用每页合计设置的 textAlign;
135
+ // 与打印账户总计金额支持共存(2026-09 交互确认):拆分只作用于第一格,其余格按原 colSpan 渲染(与导出侧 paintSubtotal 行为一致)
136
+ if (get(subtotal, 'isSplitCells')) {
137
+ const item = fields[0]
138
+ // 第一格总 colSpan 拆一半给左侧文案,剩余给数值(colSpan 缺省 99 表示整行);
139
+ // 宽度分配沿用原有逻辑:各格按 store 侧 fields[n].colSpan 照画,渲染端不调宽度
140
+ const totalColSpan = item.colSpan ?? 99
141
+ const leftColSpan = Math.floor(totalColSpan / 2)
142
+ const cellStyle = {
143
+ fontWeight: 'bold',
144
+ justifyContent: flexStyle[subtotal.style?.textAlign],
145
+ ...subtotal.style
146
+ }
147
+
148
+ return (
149
+ <tr>
150
+ <td colSpan={leftColSpan}>
151
+ <div style={cellStyle} className='gm-flex-page'>
152
+ {item.name}
153
+ </div>
154
+ </td>
155
+ <td colSpan={totalColSpan - leftColSpan}>
156
+ <div style={cellStyle} className='gm-flex-page'>
157
+ <div
158
+ className={classNames('gm-flex-page', {
159
+ 'gm-flex-justify-between-page': isUpperLowerCaseSeparate,
160
+ 'gm-flex-grow-page': isUpperLowerCaseSeparate
161
+ })}
162
+ >
163
+ <span
164
+ className={
165
+ isUpperCaseBefore
166
+ ? 'gm-printer-subtotal-isUpperCaseBefore-inter'
167
+ : ''
168
+ }
169
+ >
170
+ {item.type === 'useSummarize'
171
+ ? getData(item.valueField)
172
+ : sumData2(list, item.valueField)}
173
+ </span>
174
+ {subtotal?.needUpperCase && (
175
+ <span>
176
+ {item.type === 'useSummarize'
177
+ ? '大写:' +
178
+ coverDigit2Uppercase(getData(item.valueField))
179
+ : '大写:' +
180
+ coverDigit2Uppercase(sumData2(list, item.valueField))}
181
+ </span>
182
+ )}
183
+ </div>
184
+ </div>
185
+ </td>
186
+ {/* 拆分只拆第一格;其余格(打印账户总计金额等)沿用普通多格渲染语义 */}
187
+ {_.map(fields.slice(1), (extra, extraIndex) => (
188
+ <td
189
+ colSpan={extra.colSpan ?? 99}
190
+ key={`split_extra_${extraIndex}`}
191
+ >
192
+ <div style={cellStyle} className='gm-flex-page'>
193
+ {extra.name}
194
+ <div
195
+ className={classNames('gm-flex-page', {
196
+ 'gm-flex-justify-between-page': isUpperLowerCaseSeparate,
197
+ 'gm-flex-grow-page': isUpperLowerCaseSeparate
198
+ })}
199
+ >
200
+ <span>
201
+ {/* 账户总计金额等固定取数字段走 useSummarize;拆分场景其余格不参与当页求和 */}
202
+ {extra.type === 'useSummarize'
203
+ ? getData(extra.valueField)
204
+ : ''}
205
+ </span>
206
+ {subtotal?.needUpperCase &&
207
+ extra.type === 'useSummarize' && (
208
+ <span>
209
+ {'大写:' +
210
+ coverDigit2Uppercase(getData(extra.valueField))}
211
+ </span>
212
+ )}
213
+ </div>
214
+ </div>
215
+ </td>
216
+ ))}
217
+ </tr>
218
+ )
219
+ }
220
+
137
221
  return (
138
222
  <tr>
139
223
  {_.map(fields, (item, index) => {
package/src/util.js CHANGED
@@ -86,13 +86,12 @@ function exchange(arr, target, source) {
86
86
  return arr
87
87
  }
88
88
 
89
- let timer
90
-
91
89
  function afterImgAndSvgLoaded(callback, $printer) {
92
90
  const $imgList = $printer.querySelectorAll('img')
93
91
  const $svgList = $printer.querySelectorAll('svg')
94
92
 
95
- clearTimeout(timer)
93
+ // timer 为函数内局部变量:computePages(导出测高)与 doPrint(打印)可能并发调用本函数,
94
+ // 若共享模块级 timer,后调用方会 clearTimeout 掉前一方的轮询,导致其 callback 永不执行
96
95
 
97
96
  const everyThingIsOk =
98
97
  _.every($imgList, img => img.complete) &&
@@ -100,7 +99,7 @@ function afterImgAndSvgLoaded(callback, $printer) {
100
99
  if (everyThingIsOk) {
101
100
  callback()
102
101
  } else {
103
- timer = setTimeout(afterImgAndSvgLoaded.bind(this, callback, $printer), 300)
102
+ setTimeout(afterImgAndSvgLoaded.bind(this, callback, $printer), 300)
104
103
  }
105
104
  }
106
105
 
@@ -121,7 +120,23 @@ function getOverallOrderTrHeight(overallOrder) {
121
120
  }
122
121
 
123
122
  // eslint-disable-next-line
123
+ // 成本权限脱敏值:后端对无成本权限用户的金额字段返回 '***'(商品维度混合脱敏)
124
+ const MASKED_VALUE = '***'
125
+ const isMaskedValue = v => v === MASKED_VALUE
126
+
127
+ /**
128
+ * 脱敏感知求和:任一值为 '***' 则整体返回 '***'(口径:合计不能暴露脱敏行的贡献)
129
+ * 非数字值按 0 容错,同时兼容原始数值字段与渲染后字符串
130
+ * @param {Array} values 参与求和的值
131
+ */
132
+ const maskedSum = values => {
133
+ if (values.some(isMaskedValue)) return MASKED_VALUE
134
+ return values.reduce((a, b) => a.plus(+b || 0), Big(0)).toFixed(2)
135
+ }
136
+
124
137
  const coverDigit2Uppercase = n => {
138
+ // 脱敏值直接透传:否则 Math.abs('***') 得 NaN,Big(NaN) 会抛异常
139
+ if (isMaskedValue(n)) return MASKED_VALUE
125
140
  if (_.isNil(n) || _.isNaN(n)) {
126
141
  return '-'
127
142
  }
@@ -237,16 +252,24 @@ const caclSingleDetailsPageHeight = (detailsHeights, curRemainPageHeight) => {
237
252
  * @param {*} arr
238
253
  */
239
254
  const getArrayMid = arr => {
255
+ let majority = 23
256
+
257
+ if (arr.length === 0) return majority
258
+
259
+ // 行数少时直接取最小行高,跳过多轮 Map 统计
260
+ if (arr.length <= 30) {
261
+ const min = Math.min(...arr)
262
+ if (min / 23 > 10) return 23
263
+ return min
264
+ }
265
+
240
266
  /** 数组元素出现次数的集合 */
241
267
  const mapArr = []
242
268
  const map = new Map()
243
- let majority = 23
244
269
  /** 次数相同的元素集合 */
245
270
  const majorityArr = []
246
271
  const min = Math.min(...arr)
247
272
 
248
- if (arr.length === 0) return majority
249
-
250
273
  _.forEach(arr, (val, key) => {
251
274
  if (map.has(val)) {
252
275
  map.set(val, map.get(val) + 1)
@@ -377,5 +400,8 @@ export {
377
400
  caclSingleDetailsPageHeight,
378
401
  getArrayMid,
379
402
  getColSpanLength,
380
- getOverallOrderTrHeight
403
+ getOverallOrderTrHeight,
404
+ MASKED_VALUE,
405
+ isMaskedValue,
406
+ maskedSum
381
407
  }