n20-common-lib 2.22.73 → 2.22.74

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n20-common-lib",
3
- "version": "2.22.73",
3
+ "version": "2.22.74",
4
4
  "private": false,
5
5
  "scripts": {
6
6
  "serve": "vue-cli-service serve",
@@ -75,11 +75,12 @@
75
75
  <template v-else-if="formItem.type === 'input-number'">
76
76
  <cl-input-number
77
77
  v-model="value[key]"
78
+ v-bind="numberInputProps(formItem)"
78
79
  class="input-w"
79
80
  :is-clearable="formItem.isClearable"
80
81
  :clearable="formItem.clearable === false ? false : true"
81
82
  :disabled="formItem.disabled"
82
- :max="formItem.max || 9999999999.99"
83
+ :max="formItem.max === undefined ? 9999999999.99 : formItem.max"
83
84
  :min="formItem.min || formItem.min === 0 ? formItem.min : 0"
84
85
  :d-num="formItem.dNum || formItem.dNum === 0 ? formItem.dNum : 2"
85
86
  :type="formItem.numberType || 'money'"
@@ -89,16 +90,20 @@
89
90
  <!-- 金额区间 -->
90
91
  <template v-else-if="formItem.type === 'input-number-range'">
91
92
  <cl-input-number-range
93
+ v-bind="numberInputProps(formItem)"
92
94
  class="input-w"
95
+ :type="formItem.numberType || 'money'"
96
+ :min="formItem.min"
97
+ :max="formItem.max"
93
98
  :is-clearable="formItem.isClearable === false ? false : true"
94
99
  :disabled="formItem.disabled"
95
100
  :start-value.sync="value[key][formItem['startKey'] || 'start']"
96
101
  :end-value.sync="value[key][formItem['endKey'] || 'end']"
97
102
  @change="
98
- () =>
103
+ (range) =>
99
104
  $emit(
100
105
  'valueChange',
101
- { start: value[formItem['startKey']], end: value[formItem['endKey']] },
106
+ { start: range[0], end: range[1] },
102
107
  key
103
108
  )
104
109
  "
@@ -107,19 +112,20 @@
107
112
  <!-- 利率区间 -->
108
113
  <template v-else-if="formItem.type === 'input-rate-range'">
109
114
  <cl-input-number-range
115
+ v-bind="numberInputProps(formItem)"
110
116
  class="input-w"
111
117
  :disabled="formItem.disabled"
112
118
  :is-clearable="formItem.isClearable === false ? false : true"
113
119
  :start-value.sync="value[key][formItem['startKey'] || 'start']"
114
120
  :end-value.sync="value[key][formItem['endKey'] || 'end']"
115
- type="rate"
116
- :min="0"
117
- :max="100"
121
+ :type="formItem.numberType || 'rate'"
122
+ :min="formItem.min === undefined ? 0 : formItem.min"
123
+ :max="formItem.max === undefined ? 100 : formItem.max"
118
124
  @change="
119
- () =>
125
+ (range) =>
120
126
  $emit(
121
127
  'valueChange',
122
- { start: value[formItem['startKey']], end: value[formItem['endKey']] },
128
+ { start: range[0], end: range[1] },
123
129
  key
124
130
  )
125
131
  "
@@ -316,7 +322,8 @@
316
322
  <template v-else>
317
323
  <slot :name="key" :value="formItem.value">
318
324
  <template v-if="formItem.type === 'input-number'">
319
- <span class="value">{{ formItem.value | toThousands }}</span>
325
+ <span v-if="formItem.stringMode" class="value">{{ formatStringNumber(formItem) }}</span>
326
+ <span v-else class="value">{{ formItem.value | toThousands }}</span>
320
327
  </template>
321
328
  <template v-else-if="formItem.label === $lc('子票区间')">
322
329
  <span>{{ formItem.value | formatCdRange }}</span>
@@ -341,6 +348,8 @@ import clInputNumberRange from '../InputNumber/numberRange.vue'
341
348
  import clDatePicker from '../DatePicker/index.vue'
342
349
  import clDatePickerPor from '../DatePicker/por.vue'
343
350
  import clInputSearch from '../InputSearch/index.vue'
351
+ import N from '../../utils/numberPor'
352
+ import { formatExchangeRate, parseDecimal } from '../../utils/decimal'
344
353
  /*
345
354
  1.初始化值 是否能出发change事件
346
355
  2.当前表单change 触发其他表单的修改(未在自身作用域)
@@ -430,6 +439,28 @@ export default {
430
439
  },
431
440
  created() {},
432
441
  methods: {
442
+ // 只透传数值输入参数,避免把动态字段自身的 type/value 等配置覆盖到控件。
443
+ numberInputProps(item) {
444
+ const props = {}
445
+ const keys = ['stringMode', 'maxlength', 'dNum', 'format', 'rangeAuto', 'step', 'stepStrictly', 'suffix']
446
+ keys.forEach((key) => {
447
+ if (item[key] !== undefined) props[key] = item[key]
448
+ })
449
+ return props
450
+ },
451
+ // 详情中的高精度字段与编辑控件采用同一小数位规则,不依赖宿主的金额 filter。
452
+ formatStringNumber(item) {
453
+ const digits = item.format
454
+ ? (item.format.includes('.') ? item.format.length - item.format.indexOf('.') - 1 : 0)
455
+ : (item.dNum === undefined ? 2 : item.dNum)
456
+ const decimal = parseDecimal(item.value)
457
+ const fraction = String(item.value).trim().match(/\.(\d+)$/)
458
+ const places = item.rangeAuto && decimal
459
+ ? Math.max(digits, decimal.decimalPlaces(), fraction ? fraction[1].length : 0)
460
+ : digits
461
+ const text = formatExchangeRate(item.value, places)
462
+ return text === '--' || item.numberType === 'number' ? text : N.addThousands(text)
463
+ },
433
464
  init() {
434
465
  this.$listeners.init({ branchNoList: this.branchNoList })
435
466
  },
@@ -22,11 +22,12 @@
22
22
  | 属性名 | 类型 | 默认值 | 说明 |
23
23
  | ------------ | ------------- | ----------------- | --------------------------------- |
24
24
  | value | Number/String | undefined | 绑定值 |
25
+ | stringMode | Boolean | false | 高精度模式,非空绑定值和 input/change 事件值使用字符串 |
25
26
  | type | String | 'money' | 输入框类型,可选值:'money'、'rate'、'number' |
26
27
  | maxlength | Number | 16 | 最大输入长度 |
27
- | min | Number | -9999999999999.99 | 最小值限制 |
28
- | max | Number | 9999999999999.99 | 最大值限制 |
29
- | step | Number | 1 | 步进值,按上下箭头时的增减幅度 |
28
+ | min | Number/String | -9999999999999.99 | 最小值限制;高精度模式可传字符串 |
29
+ | max | Number/String | 9999999999999.99 | 最大值限制;高精度模式可传字符串 |
30
+ | step | Number/String | 1 | 步进值;高精度模式可传字符串 |
30
31
  | stepStrictly | Boolean | false | 是否严格按步进值递增/递减 |
31
32
  | disabled | Boolean | undefined | 是否禁用 |
32
33
  | isClearable | Boolean | false | 是否可清空 |
@@ -110,3 +111,99 @@
110
111
  <InputNumber v-model="value" type="number" :step="0.5" :stepStrictly="true" />
111
112
  ```
112
113
 
114
+ ### 20 位小数汇率
115
+
116
+ `stringMode` 默认关闭,原有金额/利率的精度、数值事件保持原样。开启后,输入、回显、步进、范围比较和变更事件使用高精度十进制处理。`input` / `change` 的非空值是字符串,清空为 `undefined`;`blur` / `clear` 事件签名保持原样。
117
+
118
+ 下面 `cl-*` 是默认注册前缀;业务项目按自己的 `Vue.use(n20, { prefix })` 替换标签。
119
+
120
+ ```vue
121
+ <template>
122
+ <!-- 汇率没有百分号,显式设置 20 位精度和足够的总输入长度。 -->
123
+ <cl-input-number
124
+ v-model="exchangeRate"
125
+ string-mode
126
+ type="number"
127
+ :d-num="20"
128
+ :maxlength="35"
129
+ min="0"
130
+ step="0.00000000000000000001"
131
+ />
132
+ </template>
133
+
134
+ <script>
135
+ export default {
136
+ data() {
137
+ // 接口响应和请求中的汇率也使用字符串,避免到达组件前已被 Number 舍入。
138
+ return { exchangeRate: '1.12345678901234567890' }
139
+ }
140
+ }
141
+ </script>
142
+ ```
143
+
144
+ - `stringMode` 不改变 `dNum`、`format` 和 `maxlength` 默认值。`format` 仍优先于 `dNum`;有旧 `format` 配置时需同步调整。
145
+ - `maxlength` 统计全部字符,默认 16。20 位小数加上 `0.` 至少需要 22;按整数位、负号和小数点配置总长度。示例的 35 可容纳默认范围的 13 位整数、负号、小数点和 20 位小数。
146
+ - 默认按十进制四舍五入到配置位数并补零;`rangeAuto` 开启后至少补到配置位数,并保留更多已有小数位。需要最多 20 位时使用默认 `rangeAuto=false`。
147
+ - `min`、`max`、`step` 的字符串形式用于高精度模式;普通模式继续传 Number。`step` 使用正值。`stepStrictly` 的中点方向与原有 `Math.round` 一致。
148
+ - 可粘贴科学计数法,提交展开为普通十进制字符串。空值、NaN、Infinity、非十进制输入不作为有效数字。
149
+ - 按上下箭头改变输入框内容,提交时机与旧模式相同,在 change/blur 时同步绑定值。
150
+ - 高精度字段不要使用 `v-model.number`、`Number` 或 `parseFloat`;校验规则使用字符串或自定义十进制校验,而不是 `type: 'number'`。
151
+
152
+ ### 高精度区间与动态字段
153
+
154
+ `InputNumberRange` 通过 attrs 透传 `string-mode`、`d-num`、`maxlength`、`step` 等参数;两个端点和 `change` 数组元素都是字符串,清空端点为 `undefined`。
155
+
156
+ ```vue
157
+ <!-- 两个字符串端点互相作为上下界,第 20 位的差异也参与比较。 -->
158
+ <cl-input-number-range
159
+ :start-value.sync="startRate"
160
+ :end-value.sync="endRate"
161
+ string-mode
162
+ type="number"
163
+ :d-num="20"
164
+ :maxlength="35"
165
+ />
166
+ ```
167
+
168
+ `Filters` / `AdvancedFilter` 在 `item.props` 里设置相同参数;`DynamicField` 在字段配置里设置:
169
+
170
+ ```js
171
+ // input-number / input-number-range / input-rate-range 均支持这些数值参数。
172
+ const field = {
173
+ type: 'input-number',
174
+ numberType: 'number',
175
+ stringMode: true,
176
+ dNum: 20,
177
+ maxlength: 35,
178
+ min: '0',
179
+ step: '0.00000000000000000001'
180
+ }
181
+ ```
182
+
183
+ 动态利率区间不传配置时仍使用 rate、0~100 的旧默认值;汇率按实际单位和范围配置 `numberType`、`min`、`max`。
184
+
185
+ ### 高精度计算、列表和 Excel
186
+
187
+ ```js
188
+ import { Decimal, formatExchangeRate } from 'n20-common-lib'
189
+
190
+ // 金额计算保留中间精度,只在最终结果四舍五入到两位;返回值依然是字符串。
191
+ const amount = new Decimal('1').times('1.00499999999999999999').toFixed(2) // '1.00'
192
+ const inverseRate = new Decimal('1').div('3').toFixed(20) // '0.33333333333333333333'
193
+ const text = formatExchangeRate('1.12345678901234567890') // 原样保留 20 位
194
+
195
+ // TablePro:独立于原有 formatRate,可通过数组参数指定其他展示位数。
196
+ const proColumn = { prop: 'rate', label: '汇率', formatter: 'formatExchangeRate' }
197
+ const customColumn = { prop: 'rate', label: '汇率', formatter: ['formatExchangeRate', 12] }
198
+
199
+ // 旧 Table:新增 exchangeRate 模板格式,固定展示 20 位。
200
+ const oldColumn = { prop: 'rate', label: '汇率', formatter: '{rate|exchangeRate}' }
201
+
202
+ // toExcel 使用字符串 rows;识别上述字符串形式的汇率 formatter 时自动设置文本格式。
203
+ const rows = [{ rate: '1.12345678901234567890' }]
204
+ // 自定义或数组 formatter 的导出列可显式设置 numFmt: '@',并同样传入字符串。
205
+ ```
206
+
207
+ `formatExchangeRate(value, decimalPlaces = 20)` 不添加千分位或百分号,零正常显示,空值/非法值显示 `--`。旧 `formatRate` / `{rate|rate}` 仍为 6 位,原有 `N` 和 `numerify` 保持兼容;高精度计算与展示需显式切换到新工具。
208
+
209
+ 导出的 `Decimal` 使用独立配置:80 位有效数字、四舍五入,不修改 `decimal.js` 原有全局配置。有效数字包括整数和小数部分;业务需要不同运算精度或舍入规则时使用 `Decimal.clone(...)`,不要修改公共构造器的配置。高精度数据进入 Excel 工具前必须已经是字符串,改变单元格显示格式不能恢复之前丢失的尾数。
@@ -34,6 +34,7 @@
34
34
  import emitter from '../../utils/element-ui-emitter'
35
35
  import { $lc } from '../../utils/i18n/index'
36
36
  import N from '../../utils/numberPor'
37
+ import { Decimal, parseDecimal } from '../../utils/decimal'
37
38
 
38
39
 
39
40
  export default {
@@ -44,6 +45,11 @@ export default {
44
45
  type: [Number, String],
45
46
  default: undefined
46
47
  },
48
+ // 高精度模式显式启用,绑定值及变更事件使用十进制字符串。
49
+ stringMode: {
50
+ type: Boolean,
51
+ default: false
52
+ },
47
53
  /**
48
54
  * 输入框类型
49
55
  * - money: 金额类型,默认保留2位小数,启用千分位格式化
@@ -62,15 +68,15 @@ export default {
62
68
  default: 16
63
69
  },
64
70
  min: {
65
- type: Number,
71
+ type: [Number, String],
66
72
  default: -9999999999999.99
67
73
  },
68
74
  max: {
69
- type: Number,
75
+ type: [Number, String],
70
76
  default: 9999999999999.99
71
77
  },
72
78
  step: {
73
- type: Number,
79
+ type: [Number, String],
74
80
  default: 1
75
81
  },
76
82
  stepStrictly: {
@@ -156,6 +162,10 @@ export default {
156
162
  watch: {
157
163
  value: {
158
164
  handler(val) {
165
+ if (this.stringMode) {
166
+ this.setStringDisplay(val)
167
+ return
168
+ }
159
169
  // number类型不启用千分位格式化,但应用小数位限制
160
170
  if (this.type === 'number') {
161
171
  if (val === undefined || val === null) {
@@ -187,9 +197,54 @@ export default {
187
197
  }
188
198
  },
189
199
  immediate: true
200
+ },
201
+ fNum() {
202
+ if (this.stringMode) this.setStringDisplay(this.value)
190
203
  }
191
204
  },
192
205
  methods: {
206
+ // rangeAuto 允许已有小数位超过配置值,其余情况按配置位数四舍五入。
207
+ formatStringValue(value, originalValue = value) {
208
+ const decimal = parseDecimal(value)
209
+ if (!decimal) return ''
210
+ // Decimal 会省略尾零,rangeAuto 额外保留普通十进制输入中的原始小数长度。
211
+ const fraction = String(originalValue).trim().match(/\.(\d+)$/)
212
+ const places = this.rangeAuto
213
+ ? Math.max(this.fNum, decimal.decimalPlaces(), fraction ? fraction[1].length : 0)
214
+ : this.fNum
215
+ // 先舍入再输出,让接近零的负数规范化为零,避免显示值与事件值的符号不同。
216
+ return decimal.toDecimalPlaces(places).toFixed(places)
217
+ },
218
+ setStringDisplay(value) {
219
+ const text = this.formatStringValue(value)
220
+ this.valueStr = this.type === 'number' ? text : N.addThousands(text)
221
+ },
222
+ // 比较和严格步进使用 Decimal,避免第 20 位不同的值被当成相等。
223
+ constrainStringValue(value, strictly = false) {
224
+ let decimal = value
225
+ const step = parseDecimal(this.step)
226
+ if (strictly && step && step.gt(0)) {
227
+ // 与旧 Math.round 的负数中点规则保持一致:中点向正无穷方向取整。
228
+ decimal = decimal.div(step).toDecimalPlaces(0, Decimal.ROUND_HALF_CEIL).times(step)
229
+ }
230
+ const min = parseDecimal(this.min)
231
+ const max = parseDecimal(this.max)
232
+ if (min && decimal.lt(min)) decimal = min
233
+ if (max && decimal.gt(max)) decimal = max
234
+ return decimal
235
+ },
236
+ changeStringValue(valStr) {
237
+ const decimal = parseDecimal(valStr)
238
+ const value = decimal ? this.formatStringValue(this.constrainStringValue(decimal, this.stepStrictly), valStr) : undefined
239
+ this.setStringDisplay(value)
240
+ this.$nextTick(() => {
241
+ if (this.value !== value) {
242
+ this.$emit('input', value)
243
+ this.$emit('change', value)
244
+ this.dispatch('ElFormItem', 'el.form.change', [value])
245
+ }
246
+ })
247
+ },
193
248
  focusFn() {
194
249
  this.isFocus = true
195
250
  if (!this.disabled && this.valueStr) {
@@ -211,6 +266,16 @@ export default {
211
266
  },
212
267
  stepFn(ev) {
213
268
  if ((ev.code === 'ArrowUp' || ev.code === 'ArrowDown') && !this.disabled) {
269
+ if (this.stringMode) {
270
+ const value = parseDecimal(this.valueStr)
271
+ const step = parseDecimal(this.step)
272
+ if (value && step && step.gt(0)) {
273
+ ev.preventDefault()
274
+ const next = ev.code === 'ArrowUp' ? value.plus(step) : value.minus(step)
275
+ this.valueStr = this.formatStringValue(this.constrainStringValue(next), this.valueStr)
276
+ }
277
+ return
278
+ }
214
279
  let val = N(this.valueStr)
215
280
  if (!isNaN(val)) {
216
281
  ev.preventDefault()
@@ -231,6 +296,15 @@ export default {
231
296
  }
232
297
  },
233
298
  inputFn(valStr) {
299
+ if (this.stringMode) {
300
+ // 编辑中的符号和小数点暂时保留,完整值在失焦时统一规范化。
301
+ if (['', '-', '+', '.', '-.', '+.'].includes(valStr) || parseDecimal(valStr)) {
302
+ this.preValue = valStr
303
+ } else {
304
+ this.valueStr = this.preValue
305
+ }
306
+ return
307
+ }
234
308
  if (valStr !== '-' && isNaN(valStr.replace(/,/g, ''))) {
235
309
  this.valueStr = this.preValue
236
310
  } else {
@@ -244,6 +318,11 @@ export default {
244
318
  this.changeIng = false
245
319
  })
246
320
 
321
+ if (this.stringMode) {
322
+ this.changeStringValue(valStr)
323
+ return
324
+ }
325
+
247
326
  let val = N(valStr)
248
327
  if (isNaN(val)) {
249
328
  this.valueStr = ''
@@ -62,11 +62,12 @@ export default {
62
62
  methods: {
63
63
  startChange(val) {
64
64
  this.$emit('update:start-value', val)
65
- this.$emit('change', [this.startValue, this.endValue])
65
+ // .sync 的父组件更新尚未完成,使用本次输入值确保 change 包含最新端点。
66
+ this.$emit('change', [val, this.endValue])
66
67
  },
67
68
  endChange(val) {
68
69
  this.$emit('update:end-value', val)
69
- this.$emit('change', [this.startValue, this.endValue])
70
+ this.$emit('change', [this.startValue, val])
70
71
  },
71
72
  blurFn() {
72
73
  this.$emit('blur', [this.startValue, this.endValue])
@@ -1,5 +1,6 @@
1
1
  import numerify from 'numerify'
2
2
  import dayjs from 'dayjs'
3
+ import { formatExchangeRate } from '../../utils/decimal'
3
4
 
4
5
  function tplFn(row, sc, mck, map = {}) {
5
6
  let str = ''
@@ -10,7 +11,7 @@ function tplFn(row, sc, mck, map = {}) {
10
11
  let key = kA[0]
11
12
  if (kA.length === 1) {
12
13
  str += row[key]
13
- } else if (row[key] || row[key] === 0) {
14
+ } else if (row[key] || row[key] === 0 || kA[1] === 'exchangeRate') {
14
15
  let type = kA[1]
15
16
  switch (type) {
16
17
  case 'money':
@@ -19,6 +20,10 @@ function tplFn(row, sc, mck, map = {}) {
19
20
  case 'rate':
20
21
  str += numerify(row[key], '0.000000', Math.floor)
21
22
  break
23
+ // 汇率单独使用高精度格式,保留历史 rate 的 6 位展示规则。
24
+ case 'exchangeRate':
25
+ str += formatExchangeRate(row[key])
26
+ break
22
27
  case 'map':
23
28
  str += map[row[key]]
24
29
  break
@@ -3,6 +3,7 @@ import 'vxe-table/lib/style.css'
3
3
  import filterContent from './filterContent.vue'
4
4
  import numerify from 'numerify'
5
5
  import dayjs from 'dayjs'
6
+ import { formatExchangeRate } from '../../utils/decimal'
6
7
  import { $lc } from '../../utils/i18n/index.js'
7
8
  import zhCN from 'vxe-table/lib/locale/lang/zh-CN'
8
9
  import { getColumnField, getRowValue, getVxeCore, setVxeVersion } from './compat.js'
@@ -136,6 +137,10 @@ if (vxeFormats && typeof vxeFormats.mixin === 'function') {
136
137
  return '--'
137
138
  }
138
139
  },
140
+ // 独立的汇率格式支持字符串值,参数可指定展示的小数位数。
141
+ formatExchangeRate({ cellValue }, decimalPlaces = 20) {
142
+ return formatExchangeRate(cellValue, decimalPlaces)
143
+ },
139
144
  // 格式化时间,默认 yyyy-MM-dd HH:mm:ss
140
145
  formatDatetime({ cellValue }) {
141
146
  if (cellValue) {
@@ -143,7 +143,6 @@ import tableSetSize from '../TableSetSize/index.vue'
143
143
  import { $lc } from '../../utils/i18n/index.js'
144
144
  import { getColumnField, getEventField, isVxe320OrNewer, normalizeArray } from './compat.js'
145
145
 
146
-
147
146
  /** 将 Element 表格风格的 show-overflow-tooltip 转为 vxe-column 的 show-overflow */
148
147
  function hasOverflowTooltipKey(item) {
149
148
  return (
@@ -153,8 +152,7 @@ function hasOverflowTooltipKey(item) {
153
152
  }
154
153
 
155
154
  function applyTooltipToShowOverflow(item) {
156
- const explicitOverflow =
157
- item.showOverflow !== undefined ? item.showOverflow : item['show-overflow']
155
+ const explicitOverflow = item.showOverflow !== undefined ? item.showOverflow : item['show-overflow']
158
156
  const hasTooltipKey = hasOverflowTooltipKey(item)
159
157
  if (!hasTooltipKey && explicitOverflow === undefined) {
160
158
  return item
@@ -704,7 +702,7 @@ export default {
704
702
  // 计算所有列的基础宽度总和
705
703
  let totalBaseWidth = 0
706
704
  columns.forEach((column) => {
707
- if (column.static && column.label === $lc("操作") && !column.width && !column.minWidth) {
705
+ if (column.static && column.label === $lc('操作') && !column.width && !column.minWidth) {
708
706
  column.width = 180
709
707
  }
710
708
  if (column.type === 'checkbox') {
@@ -728,17 +726,27 @@ export default {
728
726
  baseWidth = parseInt(widthValue) || 0
729
727
  }
730
728
  column['_baseWidth_'] = baseWidth
729
+ column['width'] = `${baseWidth}px`
730
+ column['min-width'] = undefined
731
+ column['minWidth'] = undefined
731
732
  totalBaseWidth += baseWidth
732
733
  })
733
734
  // 如果所有列的基础宽度总和 >= 容器宽度,不做处理
734
- if (totalBaseWidth >= windowWidth) {
735
+ if (totalBaseWidth >= windowWidth + 200) {
735
736
  return columns
736
737
  } else {
737
738
  const seqColumns = columns.filter((item) => item.type !== 'seq' && item.type !== 'checkbox')
738
739
  if (seqColumns.length > 0) {
739
740
  seqColumns[0].width = ''
740
741
  }
741
- return columns
742
+ return columns.map((column) => {
743
+ if (!column.type) {
744
+ const rate = column['_baseWidth_'] / totalBaseWidth
745
+ column['width'] = Math.ceil(windowWidth * rate) + 35
746
+ return column
747
+ }
748
+ return column
749
+ })
742
750
  }
743
751
  }
744
752
  return calc(columns)
package/src/index.js CHANGED
@@ -134,6 +134,7 @@ import { type } from './utils/judgeType.js'
134
134
  import list2tree from './utils/list2tree'
135
135
  import { msgPor, msgboxPor } from './utils/msgboxPor.js'
136
136
  import N from './utils/numberPor.js' // 扩展Number
137
+ import { Decimal, formatExchangeRate } from './utils/decimal.js' // 高精度汇率计算与展示
137
138
  import { closeTab, linkGo, linkPush } from './utils/urlToGo'
138
139
  import { accountFormat } from './utils/accountFormat'
139
140
 
@@ -409,6 +410,8 @@ export {
409
410
  list2tree,
410
411
  monitor,
411
412
  numerify,
413
+ Decimal,
414
+ formatExchangeRate,
412
415
  operatingStatus,
413
416
  realUrl,
414
417
  refreshTab,
@@ -0,0 +1,19 @@
1
+ import DecimalJs from 'decimal.js'
2
+
3
+ // 独立配置,避免改变业务项目中 decimal.js 的全局精度;80 位为中间乘除保留余量。
4
+ export const Decimal = DecimalJs.clone({ precision: 80, rounding: DecimalJs.ROUND_HALF_UP })
5
+
6
+ // 输入和格式化共用十进制解析,不经过 Number,空值和非有限值不作为有效数字。
7
+ export function parseDecimal(value) {
8
+ if (value === undefined || value === null || value === '') return null
9
+ const text = String(value).replace(/,/g, '').trim()
10
+ if (!/^[+-]?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?$/i.test(text)) return null
11
+ const decimal = new Decimal(text)
12
+ return decimal.isFinite() ? decimal : null
13
+ }
14
+
15
+ // 汇率默认展示 20 位小数,零值正常展示,缺失或非法值使用表格统一占位符。
16
+ export function formatExchangeRate(value, decimalPlaces = 20) {
17
+ const decimal = parseDecimal(value)
18
+ return decimal ? decimal.toDecimalPlaces(decimalPlaces).toFixed(decimalPlaces) : '--'
19
+ }
@@ -45,6 +45,10 @@ export default async function toExcel() {
45
45
  if (/\|\s?rate/.test(col.formatter)) {
46
46
  _col.style.numFmt = '0.000000'
47
47
  }
48
+ // 高精度汇率按文本展示;rows 中的原值也必须为字符串。
49
+ if (/\|\s?exchangeRate/.test(col.formatter) || col.formatter === 'formatExchangeRate') {
50
+ _col.style.numFmt = '@'
51
+ }
48
52
  }
49
53
 
50
54
  cols.push(_col)