n20-common-lib 3.3.13 → 3.3.15
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
|
@@ -57,13 +57,17 @@ export default {
|
|
|
57
57
|
}
|
|
58
58
|
},
|
|
59
59
|
computed: {
|
|
60
|
+
isChinaUrban() {
|
|
61
|
+
return this.isUrban && this.countryId === 'CHN'
|
|
62
|
+
},
|
|
60
63
|
propsAs() {
|
|
61
64
|
let _prop = {
|
|
62
65
|
value: 'regionNo',
|
|
63
66
|
label: 'regionName',
|
|
64
67
|
children: 'children',
|
|
65
68
|
expandTrigger: 'hover',
|
|
66
|
-
|
|
69
|
+
// 保留二级城市的子节点后,允许独立选择二级城市和三级地区。
|
|
70
|
+
checkStrictly: this.isChinaUrban || !this.isUrban
|
|
67
71
|
}
|
|
68
72
|
return Object.assign(_prop, this.props)
|
|
69
73
|
},
|
|
@@ -75,17 +79,17 @@ export default {
|
|
|
75
79
|
},
|
|
76
80
|
streetKey() {
|
|
77
81
|
return this.countryId === 'CHN' ? '_china_street_tree_' : `_${this.countryId}_street_tree_`
|
|
82
|
+
},
|
|
83
|
+
cacheKey() {
|
|
84
|
+
// 按当前模式检查缓存,避免普通地区缓存存在时误用尚未加载的城市缓存。
|
|
85
|
+
return this.isUrban ? this.urbanKey : this.isStreet ? this.streetKey : this.areaKey
|
|
78
86
|
}
|
|
79
87
|
},
|
|
80
88
|
mounted() {
|
|
81
|
-
if (!window[this.
|
|
89
|
+
if (!window[this.cacheKey]) {
|
|
82
90
|
this.getAreaTree()
|
|
83
91
|
} else {
|
|
84
|
-
this.areaTree = this.
|
|
85
|
-
? window[this.urbanKey]
|
|
86
|
-
: this.isStreet
|
|
87
|
-
? window[this.streetKey]
|
|
88
|
-
: window[this.areaKey]
|
|
92
|
+
this.areaTree = window[this.cacheKey]
|
|
89
93
|
}
|
|
90
94
|
},
|
|
91
95
|
methods: {
|
|
@@ -100,7 +104,7 @@ export default {
|
|
|
100
104
|
if (data) {
|
|
101
105
|
let result = JSON.parse(JSON.stringify(data))
|
|
102
106
|
if (this.isUrban) {
|
|
103
|
-
window[this.urbanKey] = isUrbanFn(result)
|
|
107
|
+
window[this.urbanKey] = isUrbanFn(result, this.isChinaUrban)
|
|
104
108
|
this.areaTree = window[this.urbanKey]
|
|
105
109
|
} else if (this.isStreet) {
|
|
106
110
|
window[this.streetKey] = treeFn(result)
|
|
@@ -133,12 +137,21 @@ const treeFn = (data) => {
|
|
|
133
137
|
})
|
|
134
138
|
}
|
|
135
139
|
|
|
136
|
-
const isUrbanFn = (data) => {
|
|
140
|
+
const isUrbanFn = (data, isChinaUrban = false) => {
|
|
137
141
|
return data?.map((res) => {
|
|
138
|
-
|
|
142
|
+
const regionLevel = String(res.regionLevel)
|
|
143
|
+
// 直辖市只保留一级节点,直接返回接口原有的两位 regionNo,不伪造下级编码。
|
|
144
|
+
const isMunicipality =
|
|
145
|
+
isChinaUrban && regionLevel === '1' && ['11', '12', '31', '50'].includes(String(res.regionNo))
|
|
146
|
+
if (isChinaUrban && regionLevel === '1' && !isMunicipality) {
|
|
147
|
+
// 普通省份仅用于展开;checkStrictly 模式下禁用父节点不影响子节点选择。
|
|
148
|
+
res.disabled = true
|
|
149
|
+
}
|
|
150
|
+
if (isMunicipality || (!isChinaUrban && regionLevel === '2') || !res.children?.length) {
|
|
139
151
|
res.children = null
|
|
140
152
|
} else {
|
|
141
|
-
|
|
153
|
+
// 保留省代管县级市等三级节点,解除原先在二级统一截断的限制。
|
|
154
|
+
res.children = isUrbanFn(res.children, isChinaUrban)
|
|
142
155
|
}
|
|
143
156
|
return res
|
|
144
157
|
})
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<el-descriptions class="n20-descriptions" :column="
|
|
2
|
+
<el-descriptions class="n20-descriptions" :column="currentColumn" size="default" border>
|
|
3
3
|
<slot></slot>
|
|
4
|
-
<template v-if="this.$slots.default.length === 1">
|
|
5
|
-
<el-descriptions-item v-for="(item, index) in
|
|
4
|
+
<template v-if="this.$slots.default && this.$slots.default.length === 1">
|
|
5
|
+
<el-descriptions-item v-for="(item, index) in currentColumn - 1" :key="index" />
|
|
6
6
|
</template>
|
|
7
7
|
</el-descriptions>
|
|
8
8
|
</template>
|
|
@@ -20,28 +20,48 @@ export default {
|
|
|
20
20
|
default: true
|
|
21
21
|
}
|
|
22
22
|
},
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
this.
|
|
26
|
-
|
|
23
|
+
data() {
|
|
24
|
+
return {
|
|
25
|
+
currentColumn: this.column
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
watch: {
|
|
29
|
+
column(val) {
|
|
30
|
+
this.currentColumn = val
|
|
31
|
+
},
|
|
32
|
+
autoResize(val) {
|
|
33
|
+
if (val) {
|
|
34
|
+
this.createResize()
|
|
35
|
+
} else {
|
|
36
|
+
this.currentColumn = this.column
|
|
37
|
+
this.destroyResize()
|
|
38
|
+
}
|
|
27
39
|
}
|
|
28
40
|
},
|
|
41
|
+
created() {
|
|
42
|
+
if (!this.autoResize) return
|
|
43
|
+
this.createResize()
|
|
44
|
+
},
|
|
29
45
|
beforeDestroy() {
|
|
30
|
-
|
|
46
|
+
this.destroyResize()
|
|
31
47
|
},
|
|
32
48
|
methods: {
|
|
33
49
|
resize() {
|
|
34
50
|
let wW = document.documentElement.clientWidth
|
|
35
51
|
if (wW <= 1024) {
|
|
36
|
-
this.
|
|
52
|
+
this.currentColumn = 1
|
|
37
53
|
} else if (wW > 1024 && wW <= 1600) {
|
|
38
|
-
this.
|
|
54
|
+
this.currentColumn = 2
|
|
39
55
|
} else if (wW > 1600 && wW <= 1920) {
|
|
40
|
-
this.
|
|
56
|
+
this.currentColumn = 3
|
|
41
57
|
} else if (wW > 1920) {
|
|
42
|
-
this.
|
|
58
|
+
this.currentColumn = 4
|
|
43
59
|
}
|
|
44
60
|
},
|
|
61
|
+
createResize() {
|
|
62
|
+
this.resize()
|
|
63
|
+
window.addEventListener('resize', this.resize)
|
|
64
|
+
},
|
|
45
65
|
destroyResize() {
|
|
46
66
|
window.removeEventListener('resize', this.resize)
|
|
47
67
|
}
|
|
@@ -31,10 +31,11 @@
|
|
|
31
31
|
| disabled | Boolean | undefined | 是否禁用 |
|
|
32
32
|
| isClearable | Boolean | false | 是否可清空 |
|
|
33
33
|
| placeholder | String | '请输入' | 占位提示文本 |
|
|
34
|
-
| dNum | Number | undefined | 小数位数,优先级低于format
|
|
34
|
+
| dNum | Number | undefined | 小数位数,优先级低于format;超过15位时以字符串保持精度 |
|
|
35
35
|
| format | String | undefined | 格式化字符串,如'0.00'表示保留2位小数 |
|
|
36
36
|
| suffix | String | '%' | 后缀文本,仅在type='rate'时有效 |
|
|
37
37
|
| rangeAuto | Boolean | false | 是否自动处理范围值 |
|
|
38
|
+
| isString | Boolean | false | 是否始终以字符串回传 input/change 值;小数位超过15位时自动按 true 处理 |
|
|
38
39
|
|
|
39
40
|
## 类型说明
|
|
40
41
|
|
|
@@ -60,6 +61,8 @@
|
|
|
60
61
|
|
|
61
62
|
## 事件
|
|
62
63
|
|
|
64
|
+
当 `dNum` 或 `format` 设置为超过 15 位小数时,组件会强制按 `isString=true` 处理,即使未显式配置 `isString`,`input` 和 `change` 也会回传字符串,避免 JavaScript `Number` 丢失精度;失焦时会按最大位数四舍五入并移除尾部 `0`,例如 `0.200000` 会回传 `"0.2"`。设置 `isString` 为 `true` 后,无论小数位数多少,`input` 和 `change` 都回传字符串;清空时回传空字符串。
|
|
65
|
+
|
|
63
66
|
| 事件名 | 说明 | 回调参数 |
|
|
64
67
|
| ------ | ----------- | ---- |
|
|
65
68
|
| input | 值改变时触发 | 当前值 |
|
|
@@ -109,4 +112,3 @@
|
|
|
109
112
|
<!-- 启用步进严格模式 -->
|
|
110
113
|
<InputNumber v-model="value" type="number" :step="0.5" :stepStrictly="true" />
|
|
111
114
|
```
|
|
112
|
-
|
|
@@ -33,7 +33,9 @@
|
|
|
33
33
|
<script>
|
|
34
34
|
import emitter from '../../utils/element-ui-emitter'
|
|
35
35
|
import { $lc } from '../../utils/i18n/index'
|
|
36
|
-
import N from '../../utils/numberPor'
|
|
36
|
+
import N, { addDecimal, compareDecimal, decimalToFixed, normalizeDecimal } from '../../utils/numberPor'
|
|
37
|
+
|
|
38
|
+
const MAX_SAFE_DECIMAL_PLACES = 15
|
|
37
39
|
|
|
38
40
|
export default {
|
|
39
41
|
name: 'InputNumber',
|
|
@@ -103,6 +105,10 @@ export default {
|
|
|
103
105
|
rangeAuto: {
|
|
104
106
|
type: Boolean,
|
|
105
107
|
default: false
|
|
108
|
+
},
|
|
109
|
+
isString: {
|
|
110
|
+
type: Boolean,
|
|
111
|
+
default: false
|
|
106
112
|
}
|
|
107
113
|
},
|
|
108
114
|
data() {
|
|
@@ -155,6 +161,20 @@ export default {
|
|
|
155
161
|
watch: {
|
|
156
162
|
value: {
|
|
157
163
|
handler(val) {
|
|
164
|
+
if (this.isHighPrecision()) {
|
|
165
|
+
if (val === undefined || val === null || val === '') {
|
|
166
|
+
this.valueStr = ''
|
|
167
|
+
} else {
|
|
168
|
+
const normalized = normalizeDecimal(val)
|
|
169
|
+
if (!normalized) {
|
|
170
|
+
this.valueStr = ''
|
|
171
|
+
return
|
|
172
|
+
}
|
|
173
|
+
const formatted = this.normalizeHighPrecisionValue(normalized)
|
|
174
|
+
this.valueStr = this.type === 'number' ? formatted : N.addThousands(formatted)
|
|
175
|
+
}
|
|
176
|
+
return
|
|
177
|
+
}
|
|
158
178
|
// number类型不启用千分位格式化,但应用小数位限制
|
|
159
179
|
if (this.type === 'number') {
|
|
160
180
|
if (val === undefined || val === null) {
|
|
@@ -189,6 +209,23 @@ export default {
|
|
|
189
209
|
}
|
|
190
210
|
},
|
|
191
211
|
methods: {
|
|
212
|
+
isHighPrecision() {
|
|
213
|
+
return this.fNum > MAX_SAFE_DECIMAL_PLACES
|
|
214
|
+
},
|
|
215
|
+
// 高精度小数会强制使用字符串回传,即使 isString 未开启。
|
|
216
|
+
shouldEmitString() {
|
|
217
|
+
return this.isString || this.isHighPrecision()
|
|
218
|
+
},
|
|
219
|
+
normalizeHighPrecisionValue(value) {
|
|
220
|
+
const normalized = normalizeDecimal(value)
|
|
221
|
+
if (!normalized) return ''
|
|
222
|
+
|
|
223
|
+
const decimalPart = normalized.split('.')[1] || ''
|
|
224
|
+
const roundedValue = this.rangeAuto && decimalPart.length >= this.fNum
|
|
225
|
+
? normalized
|
|
226
|
+
: decimalToFixed(normalized, this.fNum)
|
|
227
|
+
return normalizeDecimal(roundedValue)
|
|
228
|
+
},
|
|
192
229
|
focusFn() {
|
|
193
230
|
this.isFocus = true
|
|
194
231
|
if (!this.disabled && this.valueStr) {
|
|
@@ -210,6 +247,24 @@ export default {
|
|
|
210
247
|
},
|
|
211
248
|
stepFn(ev) {
|
|
212
249
|
if ((ev.code === 'ArrowUp' || ev.code === 'ArrowDown') && !this.disabled) {
|
|
250
|
+
if (this.isHighPrecision()) {
|
|
251
|
+
const currentValue = normalizeDecimal(this.valueStr)
|
|
252
|
+
if (!currentValue) return
|
|
253
|
+
|
|
254
|
+
ev.preventDefault()
|
|
255
|
+
const stepValue = N.toString(this.step)
|
|
256
|
+
let nextValue = addDecimal(currentValue, ev.code === 'ArrowUp' ? stepValue : '-' + stepValue)
|
|
257
|
+
if (compareDecimal(nextValue, this.min) < 0) {
|
|
258
|
+
nextValue = normalizeDecimal(this.min)
|
|
259
|
+
} else if (compareDecimal(nextValue, this.max) > 0) {
|
|
260
|
+
nextValue = normalizeDecimal(this.max)
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const formatted = this.normalizeHighPrecisionValue(nextValue)
|
|
264
|
+
this.valueStr = this.type === 'number' ? formatted : N.addThousands(formatted)
|
|
265
|
+
return
|
|
266
|
+
}
|
|
267
|
+
|
|
213
268
|
let val = N(this.valueStr)
|
|
214
269
|
if (!isNaN(val)) {
|
|
215
270
|
ev.preventDefault()
|
|
@@ -243,14 +298,51 @@ export default {
|
|
|
243
298
|
this.changeIng = false
|
|
244
299
|
})
|
|
245
300
|
|
|
301
|
+
if (this.isHighPrecision()) {
|
|
302
|
+
const normalized = normalizeDecimal(valStr)
|
|
303
|
+
if (!normalized) {
|
|
304
|
+
this.valueStr = ''
|
|
305
|
+
const oldValue = this.value
|
|
306
|
+
if (oldValue || oldValue === 0 || oldValue === '0') {
|
|
307
|
+
const emptyValue = this.shouldEmitString() ? '' : undefined
|
|
308
|
+
this.$emit('input', emptyValue)
|
|
309
|
+
this.$emit('change', emptyValue)
|
|
310
|
+
this.dispatch('ElFormItem', 'el.form.change', [emptyValue])
|
|
311
|
+
}
|
|
312
|
+
return
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
let nextValue = this.normalizeHighPrecisionValue(normalized)
|
|
316
|
+
|
|
317
|
+
if (compareDecimal(nextValue, this.min) < 0) {
|
|
318
|
+
nextValue = normalizeDecimal(this.min)
|
|
319
|
+
} else if (compareDecimal(nextValue, this.max) > 0) {
|
|
320
|
+
nextValue = normalizeDecimal(this.max)
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const formatted = this.type === 'number' ? nextValue : N.addThousands(nextValue)
|
|
324
|
+
this.valueStr = formatted
|
|
325
|
+
this.$nextTick(() => {
|
|
326
|
+
const oldValue = normalizeDecimal(this.value)
|
|
327
|
+
const normalizedNextValue = normalizeDecimal(nextValue)
|
|
328
|
+
if (oldValue !== normalizedNextValue) {
|
|
329
|
+
this.$emit('input', nextValue)
|
|
330
|
+
this.$emit('change', nextValue)
|
|
331
|
+
this.dispatch('ElFormItem', 'el.form.change', [nextValue])
|
|
332
|
+
}
|
|
333
|
+
})
|
|
334
|
+
return
|
|
335
|
+
}
|
|
336
|
+
|
|
246
337
|
let val = N(valStr)
|
|
247
338
|
if (isNaN(val)) {
|
|
248
339
|
this.valueStr = ''
|
|
249
340
|
let oVal = this.value
|
|
250
341
|
if (oVal || oVal === 0) {
|
|
251
|
-
this
|
|
252
|
-
this.$emit('
|
|
253
|
-
this
|
|
342
|
+
const emptyValue = this.shouldEmitString() ? '' : undefined
|
|
343
|
+
this.$emit('input', emptyValue)
|
|
344
|
+
this.$emit('change', emptyValue)
|
|
345
|
+
this.dispatch('ElFormItem', 'el.form.change', [emptyValue])
|
|
254
346
|
}
|
|
255
347
|
return
|
|
256
348
|
}
|
|
@@ -294,10 +386,12 @@ export default {
|
|
|
294
386
|
this.$nextTick(() => {
|
|
295
387
|
let nVar = Number(nStr)
|
|
296
388
|
let oVal = this.value
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
this
|
|
389
|
+
const emittedValue = this.shouldEmitString() ? nStr : nVar
|
|
390
|
+
const valueChanged = this.shouldEmitString() ? String(oVal) !== nStr : oVal !== nVar
|
|
391
|
+
if (valueChanged) {
|
|
392
|
+
this.$emit('input', emittedValue)
|
|
393
|
+
this.$emit('change', emittedValue)
|
|
394
|
+
this.dispatch('ElFormItem', 'el.form.change', [emittedValue])
|
|
301
395
|
}
|
|
302
396
|
})
|
|
303
397
|
}
|
package/src/utils/numberPor.js
CHANGED
|
@@ -197,6 +197,198 @@ export const subFixed = function (n, decimal = 6) {
|
|
|
197
197
|
return XEUtils.toFixed(n, decimal)
|
|
198
198
|
}
|
|
199
199
|
}
|
|
200
|
+
|
|
201
|
+
const decimalPattern = /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)$/
|
|
202
|
+
|
|
203
|
+
function padRight(value, length) {
|
|
204
|
+
return value + '0'.repeat(Math.max(0, length - value.length))
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function padLeft(value, length) {
|
|
208
|
+
return '0'.repeat(Math.max(0, length - value.length)) + value
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* 规范化十进制数字字符串,不经过 Number,避免高精度小数丢失精度。
|
|
213
|
+
* @param {number|string} value
|
|
214
|
+
* @returns {string}
|
|
215
|
+
*/
|
|
216
|
+
export const normalizeDecimal = function (value) {
|
|
217
|
+
if (value === undefined || value === null || value === '') return ''
|
|
218
|
+
|
|
219
|
+
const stringValue = toString(value).replace(/,/g, '').trim()
|
|
220
|
+
if (!decimalPattern.test(stringValue)) return ''
|
|
221
|
+
|
|
222
|
+
const sign = stringValue.charAt(0) === '-' ? '-' : ''
|
|
223
|
+
const unsignedValue = stringValue.replace(/^[+-]/, '')
|
|
224
|
+
let [integerPart = '0', decimalPart = ''] = unsignedValue.split('.')
|
|
225
|
+
integerPart = integerPart.replace(/^0+(?=\d)/, '') || '0'
|
|
226
|
+
decimalPart = decimalPart.replace(/0+$/, '')
|
|
227
|
+
|
|
228
|
+
return sign + integerPart + (decimalPart ? '.' + decimalPart : '')
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function compareUnsignedDecimal(a, b) {
|
|
232
|
+
let [aInteger = '0', aDecimal = ''] = a.split('.')
|
|
233
|
+
let [bInteger = '0', bDecimal = ''] = b.split('.')
|
|
234
|
+
aInteger = aInteger.replace(/^0+(?=\d)/, '') || '0'
|
|
235
|
+
bInteger = bInteger.replace(/^0+(?=\d)/, '') || '0'
|
|
236
|
+
|
|
237
|
+
if (aInteger.length !== bInteger.length) {
|
|
238
|
+
return aInteger.length > bInteger.length ? 1 : -1
|
|
239
|
+
}
|
|
240
|
+
if (aInteger !== bInteger) return aInteger > bInteger ? 1 : -1
|
|
241
|
+
|
|
242
|
+
const decimalLength = Math.max(aDecimal.length, bDecimal.length)
|
|
243
|
+
aDecimal = padRight(aDecimal, decimalLength)
|
|
244
|
+
bDecimal = padRight(bDecimal, decimalLength)
|
|
245
|
+
if (aDecimal === bDecimal) return 0
|
|
246
|
+
return aDecimal > bDecimal ? 1 : -1
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* 比较两个十进制数字字符串。
|
|
251
|
+
* @param {number|string} left
|
|
252
|
+
* @param {number|string} right
|
|
253
|
+
* @returns {number} -1、0、1
|
|
254
|
+
*/
|
|
255
|
+
export const compareDecimal = function (left, right) {
|
|
256
|
+
const a = normalizeDecimal(left)
|
|
257
|
+
const b = normalizeDecimal(right)
|
|
258
|
+
if (!a || !b) return NaN
|
|
259
|
+
|
|
260
|
+
const aNegative = a.charAt(0) === '-'
|
|
261
|
+
const bNegative = b.charAt(0) === '-'
|
|
262
|
+
const aUnsigned = a.replace(/^-/, '')
|
|
263
|
+
const bUnsigned = b.replace(/^-/, '')
|
|
264
|
+
|
|
265
|
+
if (aNegative !== bNegative) return aNegative ? -1 : 1
|
|
266
|
+
const result = compareUnsignedDecimal(aUnsigned, bUnsigned)
|
|
267
|
+
return aNegative ? -result : result
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function incrementUnsignedDecimal(value) {
|
|
271
|
+
const digits = value.split('')
|
|
272
|
+
for (let i = digits.length - 1; i >= 0; i -= 1) {
|
|
273
|
+
if (digits[i] === '9') {
|
|
274
|
+
digits[i] = '0'
|
|
275
|
+
} else {
|
|
276
|
+
digits[i] = String(Number(digits[i]) + 1)
|
|
277
|
+
return digits.join('')
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return '1' + digits.join('')
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* 使用字符串运算按指定小数位四舍五入,不经过 Number。
|
|
285
|
+
* @param {number|string} value
|
|
286
|
+
* @param {number} decimal
|
|
287
|
+
* @returns {string}
|
|
288
|
+
*/
|
|
289
|
+
export const decimalToFixed = function (value, decimal = 0) {
|
|
290
|
+
const normalized = normalizeDecimal(value)
|
|
291
|
+
if (!normalized) return ''
|
|
292
|
+
|
|
293
|
+
const precision = Math.max(0, Number(decimal) || 0)
|
|
294
|
+
const negative = normalized.charAt(0) === '-'
|
|
295
|
+
const unsignedValue = normalized.replace(/^-/, '')
|
|
296
|
+
let [integerPart = '0', decimalPart = ''] = unsignedValue.split('.')
|
|
297
|
+
const shouldRound = decimalPart.length > precision && decimalPart.charAt(precision) >= '5'
|
|
298
|
+
decimalPart = padRight(decimalPart.slice(0, precision), precision)
|
|
299
|
+
|
|
300
|
+
if (shouldRound) {
|
|
301
|
+
const incremented = incrementUnsignedDecimal(integerPart + decimalPart)
|
|
302
|
+
integerPart = incremented.slice(0, Math.max(1, incremented.length - precision))
|
|
303
|
+
decimalPart = precision ? padLeft(incremented.slice(-precision), precision) : ''
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return (negative ? '-' : '') + integerPart + (precision ? '.' + decimalPart : '')
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function addUnsignedDecimal(left, right) {
|
|
310
|
+
let [leftInteger = '0', leftDecimal = ''] = left.split('.')
|
|
311
|
+
let [rightInteger = '0', rightDecimal = ''] = right.split('.')
|
|
312
|
+
const decimalLength = Math.max(leftDecimal.length, rightDecimal.length)
|
|
313
|
+
leftDecimal = padRight(leftDecimal, decimalLength)
|
|
314
|
+
rightDecimal = padRight(rightDecimal, decimalLength)
|
|
315
|
+
|
|
316
|
+
let carry = 0
|
|
317
|
+
let result = ''
|
|
318
|
+
const leftDigits = (leftInteger + leftDecimal).split('')
|
|
319
|
+
const rightDigits = (rightInteger + rightDecimal).split('')
|
|
320
|
+
const length = Math.max(leftDigits.length, rightDigits.length)
|
|
321
|
+
for (let i = 0; i < length; i += 1) {
|
|
322
|
+
const leftDigit = Number(leftDigits[leftDigits.length - 1 - i] || 0)
|
|
323
|
+
const rightDigit = Number(rightDigits[rightDigits.length - 1 - i] || 0)
|
|
324
|
+
const sum = leftDigit + rightDigit + carry
|
|
325
|
+
result = String(sum % 10) + result
|
|
326
|
+
carry = Math.floor(sum / 10)
|
|
327
|
+
}
|
|
328
|
+
if (carry) result = String(carry) + result
|
|
329
|
+
|
|
330
|
+
const integerLength = result.length - decimalLength
|
|
331
|
+
const integerPart = result.slice(0, Math.max(1, integerLength))
|
|
332
|
+
const decimalPart = decimalLength ? result.slice(-decimalLength) : ''
|
|
333
|
+
return integerPart + (decimalPart ? '.' + decimalPart : '')
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function subtractUnsignedDecimal(left, right) {
|
|
337
|
+
let [leftInteger = '0', leftDecimal = ''] = left.split('.')
|
|
338
|
+
let [rightInteger = '0', rightDecimal = ''] = right.split('.')
|
|
339
|
+
const decimalLength = Math.max(leftDecimal.length, rightDecimal.length)
|
|
340
|
+
leftDecimal = padRight(leftDecimal, decimalLength)
|
|
341
|
+
rightDecimal = padRight(rightDecimal, decimalLength)
|
|
342
|
+
|
|
343
|
+
let borrow = 0
|
|
344
|
+
let result = ''
|
|
345
|
+
const leftDigits = (leftInteger + leftDecimal).split('')
|
|
346
|
+
const rightDigits = (rightInteger + rightDecimal).split('')
|
|
347
|
+
const length = Math.max(leftDigits.length, rightDigits.length)
|
|
348
|
+
for (let i = 0; i < length; i += 1) {
|
|
349
|
+
let leftDigit = Number(leftDigits[leftDigits.length - 1 - i] || 0) - borrow
|
|
350
|
+
const rightDigit = Number(rightDigits[rightDigits.length - 1 - i] || 0)
|
|
351
|
+
if (leftDigit < rightDigit) {
|
|
352
|
+
leftDigit += 10
|
|
353
|
+
borrow = 1
|
|
354
|
+
} else {
|
|
355
|
+
borrow = 0
|
|
356
|
+
}
|
|
357
|
+
result = String(leftDigit - rightDigit) + result
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const integerLength = result.length - decimalLength
|
|
361
|
+
const integerPart = result.slice(0, Math.max(1, integerLength)).replace(/^0+(?=\d)/, '') || '0'
|
|
362
|
+
const decimalPart = decimalLength ? result.slice(-decimalLength) : ''
|
|
363
|
+
return integerPart + (decimalPart ? '.' + decimalPart : '')
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* 高精度十进制加法,不经过 Number。
|
|
368
|
+
* @param {number|string} left
|
|
369
|
+
* @param {number|string} right
|
|
370
|
+
* @returns {string}
|
|
371
|
+
*/
|
|
372
|
+
export const addDecimal = function (left, right) {
|
|
373
|
+
const a = normalizeDecimal(left)
|
|
374
|
+
const b = normalizeDecimal(right)
|
|
375
|
+
if (!a || !b) return ''
|
|
376
|
+
|
|
377
|
+
const aNegative = a.charAt(0) === '-'
|
|
378
|
+
const bNegative = b.charAt(0) === '-'
|
|
379
|
+
const aUnsigned = a.replace(/^-/, '')
|
|
380
|
+
const bUnsigned = b.replace(/^-/, '')
|
|
381
|
+
|
|
382
|
+
if (aNegative === bNegative) {
|
|
383
|
+
const result = addUnsignedDecimal(aUnsigned, bUnsigned)
|
|
384
|
+
return (aNegative ? '-' : '') + normalizeDecimal(result)
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const comparison = compareUnsignedDecimal(aUnsigned, bUnsigned)
|
|
388
|
+
if (comparison === 0) return '0'
|
|
389
|
+
if (comparison > 0) return (aNegative ? '-' : '') + normalizeDecimal(subtractUnsignedDecimal(aUnsigned, bUnsigned))
|
|
390
|
+
return (bNegative ? '-' : '') + normalizeDecimal(subtractUnsignedDecimal(bUnsigned, aUnsigned))
|
|
391
|
+
}
|
|
200
392
|
/**
|
|
201
393
|
* 将数字或字符串格式的数字转换为千位分隔符格式的字符串
|
|
202
394
|
* @param {number|string} number - 待转换的数字或字符串格式的数字
|