@nstc-business/imes 1.0.36 → 1.0.37
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
|
@@ -141,8 +141,6 @@ export function sumIndicatorScores(nodes) {
|
|
|
141
141
|
items.forEach(n => {
|
|
142
142
|
const val = nodeScoreValue(n)
|
|
143
143
|
if (val === null) return
|
|
144
|
-
const num = Number(val)
|
|
145
|
-
if (Number.isNaN(num)) return
|
|
146
144
|
has = true
|
|
147
145
|
total += num
|
|
148
146
|
})
|
|
@@ -238,9 +236,7 @@ export function modelResultDigits(modelWrap) {
|
|
|
238
236
|
*/
|
|
239
237
|
export function formatModelResultText(val, modelWrap, options = {}) {
|
|
240
238
|
if (val === '' || val == null) return ''
|
|
241
|
-
|
|
242
|
-
if (Number.isNaN(num)) return String(val)
|
|
243
|
-
let text = formatFixedDecimal(num, modelResultDigits(modelWrap))
|
|
239
|
+
let text = formatFixedDecimal(val, modelResultDigits(modelWrap))
|
|
244
240
|
if (options.thousands) {
|
|
245
241
|
text = addThousands(text)
|
|
246
242
|
}
|
|
@@ -254,9 +250,7 @@ export function formatModelResultText(val, modelWrap, options = {}) {
|
|
|
254
250
|
*/
|
|
255
251
|
export function formatScoreText(val, options = {}) {
|
|
256
252
|
if (val === '' || val == null) return ''
|
|
257
|
-
|
|
258
|
-
if (Number.isNaN(num)) return String(val)
|
|
259
|
-
let text = formatFixedDecimal(num, SCORE_DIGITS)
|
|
253
|
+
let text = formatFixedDecimal(val, SCORE_DIGITS)
|
|
260
254
|
if (options.thousands) {
|
|
261
255
|
text = addThousands(text)
|
|
262
256
|
}
|
|
@@ -1,24 +1,146 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
2
|
+
<div
|
|
3
|
+
class="n20-num-w"
|
|
4
|
+
@mouseenter="isHove = true"
|
|
5
|
+
@mouseleave="isHove = false"
|
|
6
|
+
>
|
|
7
|
+
<el-input
|
|
8
|
+
ref="input"
|
|
9
|
+
v-model="valueStr"
|
|
10
|
+
v-bind="$attrs"
|
|
11
|
+
class="n20-stc"
|
|
12
|
+
:disabled="disabled"
|
|
13
|
+
:placeholder="phd"
|
|
14
|
+
:clearable="isClearable"
|
|
15
|
+
:validate-event="false"
|
|
16
|
+
@focus="focusFn"
|
|
17
|
+
@input="inputFn"
|
|
18
|
+
@blur="blurFn"
|
|
19
|
+
@clear="clearFn"
|
|
20
|
+
@change="commit"
|
|
21
|
+
@keydown.native="stepFn"
|
|
22
|
+
@copy.native="onCopy"
|
|
23
|
+
@cut.native="onCopy"
|
|
24
|
+
>
|
|
25
|
+
<template v-if="type === 'rate'">
|
|
26
|
+
<span v-if="suffixV" slot="suffix" class="el-input__icon">{{
|
|
27
|
+
suffix
|
|
28
|
+
}}</span>
|
|
29
|
+
</template>
|
|
30
|
+
</el-input>
|
|
31
|
+
</div>
|
|
9
32
|
</template>
|
|
10
33
|
|
|
11
34
|
<script>
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
35
|
+
import { formatFixedDecimal } from './numberFormat'
|
|
36
|
+
import { resolveNumberClipboardText, clearInputMaxlength } from './numberCopy'
|
|
37
|
+
import { addDecimalText } from './numberStep'
|
|
38
|
+
import { addThousands } from '../../common'
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 输入过程允许的中间态(去千分位后校验):空、-、.、1、1.、.5、-1.2 等
|
|
42
|
+
*/
|
|
43
|
+
const NUM_RE = /^[+-]?(\d*(\.\d*)?|\.\d*)$/
|
|
17
44
|
|
|
18
45
|
export default {
|
|
19
46
|
name: 'InputNumber',
|
|
20
47
|
inheritAttrs: false,
|
|
21
|
-
|
|
48
|
+
props: {
|
|
49
|
+
value: {
|
|
50
|
+
type: [Number, String],
|
|
51
|
+
default: undefined
|
|
52
|
+
},
|
|
53
|
+
type: {
|
|
54
|
+
type: String,
|
|
55
|
+
default: 'money',
|
|
56
|
+
validator(v) {
|
|
57
|
+
return ['money', 'rate'].includes(v)
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
min: {
|
|
61
|
+
type: Number,
|
|
62
|
+
default: -9999999999999.99
|
|
63
|
+
},
|
|
64
|
+
max: {
|
|
65
|
+
type: Number,
|
|
66
|
+
default: 9999999999999.99
|
|
67
|
+
},
|
|
68
|
+
step: {
|
|
69
|
+
type: Number,
|
|
70
|
+
default: 1
|
|
71
|
+
},
|
|
72
|
+
stepStrictly: {
|
|
73
|
+
type: Boolean,
|
|
74
|
+
default: false
|
|
75
|
+
},
|
|
76
|
+
disabled: {
|
|
77
|
+
type: Boolean,
|
|
78
|
+
default: undefined
|
|
79
|
+
},
|
|
80
|
+
isClearable: {
|
|
81
|
+
type: Boolean,
|
|
82
|
+
default: false
|
|
83
|
+
},
|
|
84
|
+
placeholder: {
|
|
85
|
+
type: String,
|
|
86
|
+
default: undefined
|
|
87
|
+
},
|
|
88
|
+
dNum: {
|
|
89
|
+
type: Number,
|
|
90
|
+
default: undefined
|
|
91
|
+
},
|
|
92
|
+
format: {
|
|
93
|
+
type: String,
|
|
94
|
+
default: undefined
|
|
95
|
+
},
|
|
96
|
+
suffix: {
|
|
97
|
+
type: String,
|
|
98
|
+
default: '%'
|
|
99
|
+
},
|
|
100
|
+
rangeAuto: {
|
|
101
|
+
type: Boolean,
|
|
102
|
+
default: false
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
data() {
|
|
106
|
+
return {
|
|
107
|
+
valueStr: '',
|
|
108
|
+
isHove: false,
|
|
109
|
+
isFocus: false,
|
|
110
|
+
/** 输入过程/上次提交保留的精确数字文本(去千分位),回写不经过 Number() */
|
|
111
|
+
preciseText: '',
|
|
112
|
+
preValue: ''
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
computed: {
|
|
116
|
+
suffixV() {
|
|
117
|
+
if (!this.isClearable) return true
|
|
118
|
+
if (!this.valueStr) return true
|
|
119
|
+
if (!(this.isHove || this.isFocus)) return true
|
|
120
|
+
return false
|
|
121
|
+
},
|
|
122
|
+
phd() {
|
|
123
|
+
return this.placeholder || '请输入'
|
|
124
|
+
},
|
|
125
|
+
fNum() {
|
|
126
|
+
if (this.format) {
|
|
127
|
+
const i = this.format.indexOf('.')
|
|
128
|
+
return i === -1 ? 0 : this.format.length - i - 1
|
|
129
|
+
}
|
|
130
|
+
if (this.dNum || this.dNum === 0) {
|
|
131
|
+
return this.dNum
|
|
132
|
+
}
|
|
133
|
+
return this.type === 'rate' ? 4 : 2
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
watch: {
|
|
137
|
+
value: {
|
|
138
|
+
handler() {
|
|
139
|
+
this.renderFromValue()
|
|
140
|
+
},
|
|
141
|
+
immediate: true
|
|
142
|
+
}
|
|
143
|
+
},
|
|
22
144
|
mounted() {
|
|
23
145
|
this.clearMaxlength()
|
|
24
146
|
},
|
|
@@ -57,10 +179,127 @@ export default {
|
|
|
57
179
|
)
|
|
58
180
|
: ''
|
|
59
181
|
const text = resolveNumberClipboardText(input && input.value, selected)
|
|
60
|
-
const clip =
|
|
182
|
+
const clip =
|
|
183
|
+
(e && e.clipboardData) ||
|
|
184
|
+
(e && e.originalEvent && e.originalEvent.clipboardData)
|
|
61
185
|
if (!text || text === selected || !clip) return
|
|
62
186
|
e.preventDefault()
|
|
63
187
|
clip.setData('text/plain', text)
|
|
188
|
+
},
|
|
189
|
+
/**
|
|
190
|
+
* 内部 input 当前输入(去千分位、去空格)
|
|
191
|
+
*/
|
|
192
|
+
rawText() {
|
|
193
|
+
const input = this.$el && this.$el.querySelector('input')
|
|
194
|
+
return input
|
|
195
|
+
? String(input.value || '')
|
|
196
|
+
.replace(/,/g, '')
|
|
197
|
+
.trim()
|
|
198
|
+
: ''
|
|
199
|
+
},
|
|
200
|
+
/**
|
|
201
|
+
* 外部 value 变化 -> 精确格式化展示
|
|
202
|
+
* 字符串入参直接按十进制处理(formatFixedDecimal 不经过 Number),不丢高精度
|
|
203
|
+
*/
|
|
204
|
+
renderFromValue() {
|
|
205
|
+
const val = this.value
|
|
206
|
+
if (val === '' || val == null) {
|
|
207
|
+
this.valueStr = ''
|
|
208
|
+
this.preciseText = ''
|
|
209
|
+
return
|
|
210
|
+
}
|
|
211
|
+
const text = formatFixedDecimal(val, this.fNum)
|
|
212
|
+
this.preciseText = text
|
|
213
|
+
this.valueStr = addThousands(text)
|
|
214
|
+
},
|
|
215
|
+
focusFn() {
|
|
216
|
+
this.isFocus = true
|
|
217
|
+
if (!this.disabled && this.valueStr) {
|
|
218
|
+
this.valueStr = this.valueStr.replace(/,/g, '')
|
|
219
|
+
}
|
|
220
|
+
},
|
|
221
|
+
/**
|
|
222
|
+
* 输入过程只做合法性校验,不格式化(保留完整精度)
|
|
223
|
+
*/
|
|
224
|
+
inputFn(valStr) {
|
|
225
|
+
const raw = String(valStr || '')
|
|
226
|
+
.replace(/,/g, '')
|
|
227
|
+
.trim()
|
|
228
|
+
if (raw !== '-' && raw !== '' && !NUM_RE.test(raw)) {
|
|
229
|
+
this.valueStr = this.preValue
|
|
230
|
+
} else {
|
|
231
|
+
this.preValue = valStr
|
|
232
|
+
this.preciseText = raw
|
|
233
|
+
}
|
|
234
|
+
},
|
|
235
|
+
blurFn() {
|
|
236
|
+
this.isFocus = false
|
|
237
|
+
this.commit()
|
|
238
|
+
this.$emit('blur')
|
|
239
|
+
},
|
|
240
|
+
/**
|
|
241
|
+
* 失焦提交:按 fNum 精确四舍五入(字符串路径),回写字符串值,不经过 Number()
|
|
242
|
+
*/
|
|
243
|
+
commit() {
|
|
244
|
+
if (this.disabled) return
|
|
245
|
+
const raw = this.preciseText
|
|
246
|
+
if (raw === '' || raw == null) {
|
|
247
|
+
this.valueStr = ''
|
|
248
|
+
const oVal = this.value
|
|
249
|
+
if (oVal || oVal === 0) {
|
|
250
|
+
this.$emit('input', undefined)
|
|
251
|
+
this.$emit('change', undefined)
|
|
252
|
+
}
|
|
253
|
+
return
|
|
254
|
+
}
|
|
255
|
+
let text = formatFixedDecimal(raw, this.fNum)
|
|
256
|
+
// min/max 钳制(值域内 Number 比较安全;钳制值用精确字符串格式化)
|
|
257
|
+
const num = Number(text)
|
|
258
|
+
if (num < this.min) {
|
|
259
|
+
text = formatFixedDecimal(this.min, this.fNum)
|
|
260
|
+
} else if (num > this.max) {
|
|
261
|
+
text = formatFixedDecimal(this.max, this.fNum)
|
|
262
|
+
}
|
|
263
|
+
this.preciseText = text
|
|
264
|
+
this.valueStr = addThousands(text)
|
|
265
|
+
const oVal = this.value
|
|
266
|
+
if (oVal !== text) {
|
|
267
|
+
this.$emit('input', text)
|
|
268
|
+
this.$emit('change', text)
|
|
269
|
+
}
|
|
270
|
+
},
|
|
271
|
+
clearFn() {
|
|
272
|
+
this.$emit('clear')
|
|
273
|
+
},
|
|
274
|
+
/**
|
|
275
|
+
* 上下键步进:基于精确字符串做十进制加减,避免 number 累加丢精度
|
|
276
|
+
*/
|
|
277
|
+
stepFn(ev) {
|
|
278
|
+
if (
|
|
279
|
+
(ev.code === 'ArrowUp' || ev.code === 'ArrowDown') &&
|
|
280
|
+
!this.disabled
|
|
281
|
+
) {
|
|
282
|
+
const raw = this.rawText()
|
|
283
|
+
if (raw === '' || raw === '-' || raw === '.') return
|
|
284
|
+
if (!NUM_RE.test(raw)) return
|
|
285
|
+
ev.preventDefault()
|
|
286
|
+
const delta = ev.code === 'ArrowUp' ? this.step : -this.step
|
|
287
|
+
let text = addDecimalText(raw, delta)
|
|
288
|
+
if (this.stepStrictly) {
|
|
289
|
+
const num = Number(text)
|
|
290
|
+
if (num % this.step !== 0) {
|
|
291
|
+
text = String(Math.round(num / this.step) * this.step)
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
const num = Number(text)
|
|
295
|
+
if (num < this.min) {
|
|
296
|
+
text = formatFixedDecimal(this.min, this.fNum)
|
|
297
|
+
} else if (num > this.max) {
|
|
298
|
+
text = formatFixedDecimal(this.max, this.fNum)
|
|
299
|
+
}
|
|
300
|
+
this.preciseText = text
|
|
301
|
+
this.valueStr = addThousands(text)
|
|
302
|
+
}
|
|
64
303
|
}
|
|
65
304
|
}
|
|
66
305
|
}
|
|
@@ -13,19 +13,34 @@
|
|
|
13
13
|
* 人员:AI && 徐红飞
|
|
14
14
|
* 入参:val 数字或数字字符串;digits 小数位数
|
|
15
15
|
* 出参:格式化后的字符串;空值返回 '';非数字字符串原样返回
|
|
16
|
-
* 方法内容:先转最短十进制串再按位四舍五入补零,避开原生 toFixed
|
|
16
|
+
* 方法内容:先转最短十进制串再按位四舍五入补零,避开原生 toFixed 的精度噪声;
|
|
17
|
+
* 字符串输入直接按十进制字符串处理,不经过 Number()(双精度只有约 15~17 位有效数字,
|
|
18
|
+
* 字符串一旦转 number 高精度小数即被舍入,例如 '160835267.0579509644' 会变成
|
|
19
|
+
* 160835267.05795097,精度无法恢复)
|
|
17
20
|
*/
|
|
18
21
|
export function formatFixedDecimal(val, digits) {
|
|
19
22
|
if (val === '' || val == null) return ''
|
|
20
23
|
const raw = typeof val === 'string' ? val.replace(/,/g, '').trim() : val
|
|
21
24
|
if (raw === '') return ''
|
|
22
|
-
|
|
23
|
-
|
|
25
|
+
const d = digits == null || digits === '' ? 2 : Number(digits)
|
|
26
|
+
if (!Number.isFinite(d) || d < 0) return String(val)
|
|
27
|
+
|
|
28
|
+
// 字符串数字:直接按十进制字符串四舍五入,不经过 Number()
|
|
29
|
+
if (typeof raw === 'string') {
|
|
30
|
+
if (/[eE]/.test(raw)) {
|
|
31
|
+
// 科学计数法字符串(如 '1.6e8'):转 number 后展开,保持原行为
|
|
32
|
+
const n = Number(raw)
|
|
33
|
+
if (Number.isNaN(n)) return String(val)
|
|
34
|
+
return roundPlainDecimal(numberToPlainString(n), d)
|
|
35
|
+
}
|
|
36
|
+
if (!/^[+-]?(\d+(\.\d*)?|\.\d+)$/.test(raw)) {
|
|
37
|
+
return String(val) // 非数字字符串原样返回
|
|
38
|
+
}
|
|
39
|
+
return roundPlainDecimal(raw, d)
|
|
24
40
|
}
|
|
25
|
-
|
|
41
|
+
|
|
42
|
+
const n = raw
|
|
26
43
|
if (Number.isNaN(n)) return String(val)
|
|
27
|
-
const d = digits == null || digits === '' ? 2 : Number(digits)
|
|
28
|
-
if (!Number.isFinite(d) || d < 0) return String(n)
|
|
29
44
|
return roundPlainDecimal(numberToPlainString(n), d)
|
|
30
45
|
}
|
|
31
46
|
|
|
@@ -43,4 +43,23 @@ if (rounded !== '1.24') {
|
|
|
43
43
|
throw new Error('四舍五入 1.239 保留 2 位应为 1.24,实际: ' + rounded)
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
// 高精度 number 字面量:双精度只有约 15~17 位有效数字,入参在解析时已舍入为最近可表示值,
|
|
47
|
+
// 格式化结果只能基于该近似值(语言限制,非格式化层可恢复)
|
|
48
|
+
const highPrecNumber = formatFixedDecimal(160835267.0579509644, 10)
|
|
49
|
+
if (highPrecNumber !== '160835267.0579509700') {
|
|
50
|
+
throw new Error('number 入参受双精度限制应为 160835267.0579509700,实际: ' + highPrecNumber)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// 高精度数字字符串:不经过 Number(),应完整保留每一位小数
|
|
54
|
+
const highPrecString = formatFixedDecimal('160835267.0579509644', 10)
|
|
55
|
+
if (highPrecString !== '160835267.0579509644') {
|
|
56
|
+
throw new Error('字符串入参应完整保留 160835267.0579509644,实际: ' + highPrecString)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// 字符串入参四舍五入进位
|
|
60
|
+
const carry = formatFixedDecimal('9.99999', 4)
|
|
61
|
+
if (carry !== '10.0000') {
|
|
62
|
+
throw new Error('字符串进位 9.99999 保留 4 位应为 10.0000,实际: ' + carry)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
console.log('numberFormat 用例全部通过')
|