gm-printer 11.0.2-beta.2 → 11.1.0-beta.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.
@@ -4,7 +4,13 @@ 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 {
8
+ coverDigit2Uppercase,
9
+ getDataKey,
10
+ formatSumWithPrecision,
11
+ extractSumField,
12
+ templateSumResult
13
+ } from '../util'
8
14
  import { observer } from 'mobx-react'
9
15
  import { get } from 'mobx'
10
16
  import classNames from 'classnames'
@@ -45,69 +51,65 @@ const SubtotalTr = props => {
45
51
  isSomeSubtotalTr
46
52
  } = props
47
53
  const tableData = printerStore.data._table[getDataKey(dataKey, arrange)] || []
48
- // 计算合计
49
- const sumData = (list, field) => {
50
- return _.reduce(
51
- list,
52
- (a, b) => {
53
- let result = a
54
-
55
- const _origin = b._origin || {}
56
- const _origin2 = b['_origin' + MULTI_SUFFIX] || {}
57
- const _origin3 = b['_origin' + MULTI_SUFFIX3] || {}
58
-
59
- result = a.plus(_origin[field] || 0)
60
- if (_origin2[field]) {
61
- result = result.plus(_origin2[field])
62
- }
63
- if (_origin3[field]) {
64
- result = result.plus(_origin3[field])
65
- }
66
- return result
67
- },
68
- Big(0)
69
- ).toFixed(2)
70
- }
71
54
 
72
55
  // 计算合计
73
- const sumData2 = (list, field) => {
56
+ const sumData2 = (list, field, formatter, highPrecisionMapping) => {
57
+ // 在字段名转换前,查找是否有高精度字段
58
+ const hpField = highPrecisionMapping?.[field]
59
+
74
60
  // 兼容之前
75
61
  if (field === 'total_item_price') field = '下单金额'
76
62
  if (field === 'real_item_price') field = '出库金额'
77
63
  if (field === 'settle_money2') field = '结算金额'
78
64
  if (field === 'total_money2') field = '金额'
79
- return _.reduce(
65
+
66
+ const sum = _.reduce(
80
67
  list,
81
68
  (a, b) => {
82
69
  let result = a
83
- result = a.plus(b[field] || 0)
84
- if (b?.[field + MULTI_SUFFIX]) {
70
+ // 优先使用高精度字段(从 _origin 中取原始数据)
71
+ if (hpField) {
72
+ result = a.plus(b._origin?.[hpField] ?? b[hpField] ?? 0)
73
+ } else {
74
+ result = a.plus(b[field] || 0)
75
+ }
76
+ if (!hpField && b?.[field + MULTI_SUFFIX]) {
85
77
  result = result.plus(b?.[field + MULTI_SUFFIX])
86
78
  }
87
- if (b?.[field + MULTI_SUFFIX3]) {
79
+ if (!hpField && b?.[field + MULTI_SUFFIX3]) {
88
80
  result = result.plus(b?.[field + MULTI_SUFFIX3])
89
81
  }
90
82
  return result
91
83
  },
92
84
  Big(0)
93
- ).toFixed(2)
85
+ )
86
+ return formatSumWithPrecision(sum, formatter)
94
87
  }
95
88
 
96
89
  const getData = field => {
97
90
  const data = printerStore.data.common
98
91
  return data?.[field] ?? ''
99
92
  }
93
+ const formatter = printerStore.config?.payAmountFormatter
94
+ const highPrecisionMapping = printerStore.config?.highPrecisionFieldMapping
100
95
  // 每页小计
101
96
  // show 是否展示小计,fields<Array> 合计的字段和展现的name,displayName 是否显示名字
102
97
  if (show && printerStore.ready) {
103
98
  const list = tableData.slice(range.begin, range.end)
104
99
 
100
+ // 先按字段求和,再对结果做模板/内置函数渲染
101
+ const formatSumValue = valueField => {
102
+ const fieldKey = extractSumField(valueField)
103
+ const sum = sumData2(list, fieldKey, formatter, highPrecisionMapping)
104
+ return templateSumResult(valueField, sum, fieldKey)
105
+ }
106
+
105
107
  // 针对结款单的
106
108
  if (isSomeSubtotalTr) {
107
109
  const sum = {}
108
110
 
109
111
  _.each(fields, v => {
110
- sum[v.name] = sumData2(list, v.valueField)
112
+ sum[v.name] = formatSumValue(v.valueField)
111
113
  })
112
114
  let subtotalStr = ''
113
115
  for (const name in sum) {
@@ -137,6 +139,18 @@ const SubtotalTr = props => {
137
139
  return (
138
140
  <tr>
139
141
  {_.map(fields, (item, index) => {
142
+ const fieldKey = extractSumField(item.valueField)
143
+ const sumDisplay =
144
+ item.type === 'useSummarize'
145
+ ? templateSumResult(
146
+ item.valueField,
147
+ getData(fieldKey),
148
+ fieldKey
149
+ )
150
+ : index === 0
151
+ ? formatSumValue(item.valueField)
152
+ : ''
153
+
140
154
  return (
141
155
  <td colSpan={item.colSpan ?? 99} key={index}>
142
156
  <div
@@ -162,23 +176,12 @@ const SubtotalTr = props => {
162
176
  }
163
177
  >
164
178
  {/* 这样写是为了支持自定义单元,index = 1时是自定义单元格 */}
165
- {item.type === 'useSummarize'
166
- ? getData(item.valueField)
167
- : index === 0
168
- ? sumData2(list, item.valueField)
169
- : ''}
170
- {/* {index === 0 ? sumData(list, item.valueField) : ''} */}
179
+ {sumDisplay}
171
180
  </span>
172
181
  {subtotal?.needUpperCase && ( // 是否需要大写金额
173
182
  <span>
174
- {item.type === 'useSummarize'
175
- ? '大写:' +
176
- coverDigit2Uppercase(getData(item.valueField))
177
- : index === 0
178
- ? '大写:' +
179
- coverDigit2Uppercase(
180
- sumData2(list, item.valueField)
181
- )
183
+ {sumDisplay
184
+ ? '大写:' + coverDigit2Uppercase(sumDisplay)
182
185
  : ''}
183
186
  </span>
184
187
  )}
package/src/util.js CHANGED
@@ -353,6 +353,103 @@ const collectGroups = (tableData, range, key = '_collect') => {
353
353
  }
354
354
  }
355
355
 
356
+ /**
357
+ * 使用精度格式化函数格式化 Big.js 求和结果,无格式化函数时兜底 toFixed(2)
358
+ * @param {Big} sum - Big.js 实例
359
+ * @param {Function} [formatter] - 可选的精度格式化函数
360
+ * @returns {string}
361
+ */
362
+ const formatSumWithPrecision = (sum, formatter) => {
363
+ return formatter ? formatter(sum).toFixed() : sum.toFixed(2)
364
+ }
365
+
366
+ /**
367
+ * 根据高精度字段映射,获取求和时应使用的字段名
368
+ * 如果字段有对应的高精度版本,返回高精度字段名,否则返回原字段名
369
+ * @param {string} field - 原始字段名
370
+ * @param {Object} mapping - 高精度字段映射,格式:{ normalField: 'high_precision_field' }
371
+ * @returns {string}
372
+ */
373
+ const getHighPrecisionField = (field, mapping) => {
374
+ return mapping?.[field] || field
375
+ }
376
+
377
+ /**
378
+ * 打印模板内置金额格式化(与 printerStore.template 注入的 price 一致)
379
+ * @param {*} n
380
+ * @param {number} [f=2]
381
+ * @returns {string|null}
382
+ */
383
+ const price = (n, f = 2) => {
384
+ if (isNaN(n)) return null
385
+ return Big(n || 0).toFixed(f)
386
+ }
387
+
388
+ /**
389
+ * 从合计配置解析用于求和的字段名
390
+ * 支持:出库金额 / {{出库金额}} / {{列.出库金额}} / {{price(出库金额,1)}} / {{price(列.出库金额)}}
391
+ * @param {string} text
392
+ * @returns {string}
393
+ */
394
+ const extractSumField = text => {
395
+ if (text == null || text === '') return text
396
+ if (typeof text !== 'string') return text
397
+ if (!text.includes('{{')) return text
398
+
399
+ const priceMatch = text.match(
400
+ /price\s*\(\s*(?:列\.)?\s*([^,)]+?)(?:\s*,|\s*\))/
401
+ )
402
+ if (priceMatch) return priceMatch[1].trim()
403
+
404
+ const colMatch = text.match(/\{\{\s*列\.([^}]+?)\s*\}\}/)
405
+ if (colMatch) return colMatch[1].trim()
406
+
407
+ const bareMatch = text.match(/\{\{\s*([^{}()]+?)\s*\}\}/)
408
+ if (bareMatch) return bareMatch[1].trim()
409
+
410
+ return text
411
+ }
412
+
413
+ /**
414
+ * 对合计结果做 lodash 模板渲染并注入内置 price;无模板语法时原样返回合计值
415
+ * 语法错误时与 printerStore.template 一致:返回原文
416
+ * @param {string} text - valueField 或列 text
417
+ * @param {string|number} sumValue - 已求得的合计值
418
+ * @param {string} [fieldKey] - 字段名,默认从 text 解析
419
+ * @returns {string|number}
420
+ */
421
+ const templateSumResult = (text, sumValue, fieldKey) => {
422
+ if (text == null || text === '' || typeof text !== 'string') {
423
+ return sumValue
424
+ }
425
+ if (!text.includes('{{')) {
426
+ return sumValue
427
+ }
428
+
429
+ const key = fieldKey || extractSumField(text)
430
+ try {
431
+ return _.template(text, {
432
+ interpolate: /{{([\s\S]+?)}}/g
433
+ })({
434
+ price,
435
+ [key]: sumValue,
436
+ 列: { [key]: sumValue }
437
+ })
438
+ } catch (err) {
439
+ return text
440
+ }
441
+ }
442
+
443
+ /**
444
+ * 将合计配置转为用于逐行取值的列模板(去掉 price,仅保留 {{列.字段}})
445
+ * @param {string} text
446
+ * @returns {string}
447
+ */
448
+ const toSumColTemplate = text => {
449
+ const key = extractSumField(text)
450
+ return `{{列.${key}}}`
451
+ }
452
+
356
453
  export {
357
454
  collectGroups,
358
455
  getHeight,
@@ -373,5 +470,11 @@ export {
373
470
  caclSingleDetailsPageHeight,
374
471
  getArrayMid,
375
472
  getColSpanLength,
376
- getOverallOrderTrHeight
473
+ getOverallOrderTrHeight,
474
+ formatSumWithPrecision,
475
+ getHighPrecisionField,
476
+ price,
477
+ extractSumField,
478
+ templateSumResult,
479
+ toSumColTemplate
377
480
  }
@@ -1,335 +0,0 @@
1
- import i18next from '../../locales'
2
- import React from 'react'
3
- import PropTypes from 'prop-types'
4
- import CommonContextMenu from '../common/common_context_menu'
5
- import { inject, observer } from 'mobx-react'
6
- import _ from 'lodash'
7
- import { Printer } from '../printer'
8
- import { isMultiTable } from '../util'
9
- import { LONG_PRINT } from '../config'
10
-
11
- const blockTypeList = [
12
- { value: '', text: i18next.t('插入文本') },
13
- { value: 'line', text: i18next.t('插入线条') },
14
- { value: 'image', text: i18next.t('插入图片') },
15
- { value: 'counter', text: i18next.t('插入分类汇总') },
16
- { value: 'tag', text: i18next.t('插入标签汇总') },
17
- { value: 'barcode', text: i18next.t('插入订单条形码') },
18
- { value: 'qrcode', text: i18next.t('插入订单溯源二维码') },
19
- {
20
- value: 'uniform_social_credit_code',
21
- text: i18next.t('插入苏州安全监管平台二维码')
22
- }
23
- ]
24
-
25
- @inject('editStore')
26
- @observer
27
- class ContextMenu extends React.Component {
28
- componentDidMount() {
29
- const {
30
- editStore,
31
- editStore: {
32
- config: { autoFillConfig, specialControlConfig }
33
- }
34
- } = this.props
35
-
36
- if (autoFillConfig?.checked) {
37
- editStore.handleChangeTableData(
38
- autoFillConfig?.checked,
39
- autoFillConfig?.dataKey
40
- )
41
- }
42
- /** 初始化特殊控制的配置 */
43
- if (specialControlConfig?.multiDigitDecimal) {
44
- // 多位小数
45
- editStore.setMultiDigitDecimal(specialControlConfig?.multiDigitDecimal)
46
- }
47
- if (specialControlConfig?.taxFreeProductRateDisplay !== undefined) {
48
- // 免税产品税率显示
49
- editStore.setTaxFreeProductRateDisplay(
50
- specialControlConfig?.taxFreeProductRateDisplay || ''
51
- )
52
- }
53
- }
54
-
55
- isMenuItemEnabled = key => {
56
- const { menuConfig } = this.props
57
- if (!menuConfig) return true
58
- if (Array.isArray(menuConfig)) return menuConfig.includes(key)
59
- if (typeof menuConfig === 'object') return !!menuConfig[key]
60
- return true
61
- }
62
-
63
- /**
64
- * 是否存在每页合计按钮,非异常明细才有按钮
65
- * @param name => ContextMenu 的 this.state.name
66
- * @return {boolean}
67
- */
68
- hasSubtotalBtn = name => {
69
- if (!name) return false
70
- const noSubtotalList = ['abnormal', 'abnormalDetails']
71
- const arr = name.split('.')
72
- if (_.includes(arr, 'table')) {
73
- const dataKey = this.props.editStore.config.contents[arr[2]].dataKey
74
- return !_.includes(noSubtotalList, dataKey)
75
- }
76
- }
77
-
78
- handleChangeTableDataKey = (key, name) => {
79
- const { editStore } = this.props
80
-
81
- editStore.changeTableDataKey(name, key)
82
- }
83
-
84
- handleSubtotal = name => {
85
- const { editStore } = this.props
86
- editStore.setSubtotalShow(name)
87
- }
88
-
89
- handleCategoryConfig = (key, name) => {
90
- const { editStore } = this.props
91
- editStore.changeTableDataKey(name, key)
92
-
93
- editStore.setCategoryConfigShow(name)
94
- }
95
-
96
- handleChangeTableData = isAutoFilling => {
97
- const { editStore } = this.props
98
- editStore.handleChangeTableData(isAutoFilling)
99
- }
100
-
101
- handleOverallOrder = name => {
102
- const { editStore } = this.props
103
- editStore.setOverallOrderShow(name)
104
- }
105
-
106
- handleDiyOverallOrder = name => {
107
- const { editStore } = this.props
108
- editStore.setDiyOverallOrderShow(name)
109
- }
110
-
111
- // 通用的自定义合计菜单项处理
112
- handleDiySummary = (name, configKey) => {
113
- const { editStore } = this.props
114
- const methodName = configKey
115
- .split(/(?=[A-Z])/)
116
- .map(word => word.charAt(0).toUpperCase() + word.slice(1))
117
- .join('')
118
-
119
- const method = editStore[`set${methodName}Show`]
120
-
121
- if (configKey === 'diyCategorySubtotal') {
122
- const arr = name.split('.')
123
- const dataKey = editStore.config.contents[arr[2]].dataKey
124
- console.log('dataKey==> handleDiySummary', dataKey)
125
-
126
- if (!dataKey.split('_').includes('category')) {
127
- editStore.changeTableDataKey(name, 'category', {
128
- type: 'diyCategorySubtotal'
129
- })
130
- }
131
- }
132
- if (configKey === 'diyTagSubtotal') {
133
- const arr = name.split('.')
134
- const dataKey = editStore.config.contents[arr[2]].dataKey
135
- console.log('dataKey==> handleDiySummary', dataKey)
136
-
137
- if (!dataKey.split('_').includes('tag')) {
138
- editStore.changeTableDataKey(name, 'tag', {
139
- type: 'diyTagSubtotal'
140
- })
141
- }
142
- }
143
- if (method) {
144
- method(name)
145
- }
146
- }
147
-
148
- renderOrderActionBtn = name => {
149
- if (!this.hasSubtotalBtn(name)) {
150
- return null
151
- }
152
-
153
- const {
154
- editStore: {
155
- config: {
156
- page: { type },
157
- showDiyOverAllOrder
158
- },
159
- isAutoFilling
160
- }
161
- } = this.props
162
- const arr = name.split('.')
163
- const {
164
- dataKey,
165
- subtotal,
166
- overallOrder,
167
- diySubtotal,
168
- diyTagSubtotal,
169
- diyOverallOrder,
170
- diyCategorySubtotal
171
- } = this.props.editStore.config.contents[arr[2]]
172
- const keyArr = dataKey.split('_')
173
-
174
- const isMultiActive = keyArr.includes('multi')
175
- const isThreeActive = keyArr.includes('multi3')
176
- const isCategoryActive = keyArr.includes('category')
177
- const isTagActive = keyArr.includes('tag')
178
- const isNewCategoryActive = keyArr.includes('newCategory')
179
- const isSubtotalActive = subtotal.show
180
- const isOverallOrder = overallOrder?.show
181
- const isDiyOverallOrder = diyOverallOrder?.show
182
- const isDiySubtotal = diySubtotal?.show
183
- const isDiyTagSubtotal = diyTagSubtotal?.show
184
- const isDiyCategorySubtotal = diyCategorySubtotal?.show
185
-
186
- // 长条打印
187
- const isLongPrint = type === LONG_PRINT
188
-
189
- return (
190
- <>
191
- {this.isMenuItemEnabled('multi') && !isLongPrint && (
192
- <div
193
- onClick={this.handleChangeTableDataKey.bind(this, 'multi', name)}
194
- className={isMultiActive ? 'active' : ''}
195
- >
196
- {i18next.t('双栏商品')}
197
- </div>
198
- )}
199
- {this.isMenuItemEnabled('multi3') && type !== 'A4/3' && !isLongPrint && (
200
- <div
201
- onClick={this.handleChangeTableDataKey.bind(this, 'multi3', name)}
202
- className={isThreeActive ? 'active' : ''}
203
- >
204
- {i18next.t('三栏商品')}
205
- </div>
206
- )}
207
- {this.isMenuItemEnabled('category') && (
208
- <div
209
- onClick={this.handleChangeTableDataKey.bind(
210
- this,
211
- 'category',
212
- name,
213
- isCategoryActive
214
- )}
215
- className={isCategoryActive ? 'active' : ''}
216
- >
217
- {i18next.t('分类小计')}
218
- </div>
219
- )}
220
- {this.isMenuItemEnabled('categoryDiy') && (
221
- <div
222
- onClick={this.handleChangeTableDataKey.bind(
223
- this,
224
- 'categoryDiy',
225
- name
226
- )}
227
- className={isDiyCategorySubtotal ? 'active' : ''}
228
- >
229
- {i18next.t('自定义分类小计')}
230
- </div>
231
- )}
232
- {this.isMenuItemEnabled('tag') && (
233
- <div
234
- onClick={this.handleChangeTableDataKey.bind(this, 'tag', name)}
235
- className={isTagActive ? 'active' : ''}
236
- >
237
- {i18next.t('标签小计')}
238
- </div>
239
- )}
240
- {this.isMenuItemEnabled('tagDiy') && (
241
- <div
242
- onClick={this.handleChangeTableDataKey.bind(this, 'tagDiy', name)}
243
- className={isDiyTagSubtotal ? 'active' : ''}
244
- >
245
- {i18next.t('自定义标签小计')}
246
- </div>
247
- )}
248
- {this.isMenuItemEnabled('newCategory') && (
249
- <div
250
- onClick={this.handleCategoryConfig.bind(this, 'newCategory', name)}
251
- className={isNewCategoryActive ? 'active' : ''}
252
- >
253
- {i18next.t('商品分类')}
254
- </div>
255
- )}
256
- {this.isMenuItemEnabled('subtotal') && (
257
- <div
258
- onClick={this.handleSubtotal.bind(this, name)}
259
- className={isSubtotalActive ? 'active' : ''}
260
- >
261
- {i18next.t('每页合计')}
262
- </div>
263
- )}
264
- {this.isMenuItemEnabled('diySubtotal') && (
265
- <div
266
- onClick={() => this.handleDiySummary(name, 'diySubtotal')}
267
- className={isDiySubtotal ? 'active' : ''}
268
- >
269
- {i18next.t('自定义每页合计')}
270
- </div>
271
- )}
272
- {this.isMenuItemEnabled('autoFill') && (
273
- <div
274
- onClick={this.handleChangeTableData.bind(this, !isAutoFilling)}
275
- className={isAutoFilling ? 'active' : ''}
276
- >
277
- {i18next.t('行数填充')}
278
- </div>
279
- )}
280
- {this.isMenuItemEnabled('overallOrder') && (
281
- <div
282
- onClick={this.handleOverallOrder.bind(this, name)}
283
- className={isOverallOrder ? 'active' : ''}
284
- >
285
- {i18next.t('整单合计')}
286
- </div>
287
- )}
288
- {this.isMenuItemEnabled('diyOverallOrder') &&
289
- !isMultiTable(dataKey) &&
290
- showDiyOverAllOrder && (
291
- <div
292
- onClick={this.handleDiyOverallOrder.bind(this, name)}
293
- className={isDiyOverallOrder ? 'active' : ''}
294
- >
295
- {i18next.t('自定义整单合计')}
296
- </div>
297
- )}
298
- </>
299
- )
300
- }
301
-
302
- render() {
303
- const { editStore, customTableConfigs } = this.props
304
- return (
305
- <CommonContextMenu
306
- renderTableAction={this.renderOrderActionBtn}
307
- insertBlockList={blockTypeList}
308
- customTableConfigs={customTableConfigs}
309
- >
310
- <Printer
311
- key={editStore.computedPrinterKey}
312
- selected={editStore.selected}
313
- selectedRegion={editStore.selectedRegion}
314
- isAutoFilling={editStore.isAutoFilling}
315
- fillIndex={editStore.fillIndex}
316
- lineheight={editStore.computedTableCustomerRowHeight}
317
- config={editStore.config}
318
- data={editStore.mockData}
319
- getremainpageHeight={editStore.setRemainPageHeight}
320
- overallorder={editStore.overallOrderShow}
321
- />
322
- </CommonContextMenu>
323
- )
324
- }
325
- }
326
- ContextMenu.propTypes = {
327
- editStore: PropTypes.object,
328
- mockData: PropTypes.object,
329
- customTableConfigs: PropTypes.array,
330
- menuConfig: PropTypes.oneOfType([
331
- PropTypes.arrayOf(PropTypes.string),
332
- PropTypes.object
333
- ])
334
- }
335
- export default ContextMenu