UNPKG

23.6 kBJavaScriptView Raw
1import React, {Component} from 'react';
2import classnames from 'classnames';
3import InputGroup from 'bee-input-group';
4import FormControl from 'bee-form-control';
5import Message from 'bee-message';
6import PropTypes from 'prop-types';
7import i18n from './i18n';
8import { getComponentLocale } from 'bee-locale/build/tool';
9
10const propTypes = {
11 max: PropTypes.number,
12 min: PropTypes.number,
13 step: PropTypes.number,
14 autoWidth: PropTypes.bool,
15 precision: PropTypes.number,
16 format: PropTypes.func,
17 delay: PropTypes.number,
18 disabled:PropTypes.bool,
19 toThousands:PropTypes.bool,
20 locale:PropTypes.object,
21 toNumber:PropTypes.bool, //回调函数内的值是否转换为数值类型
22 displayCheckPrompt:PropTypes.bool, //是否显示超出限制范围之后的检验提示
23 minusRight:PropTypes.bool,//负号是否在右边
24 handleBtnClick:PropTypes.func,//加减按钮点击回调
25};
26
27const defaultProps = {
28 value: "",
29 step: 1,
30 clsPrefix: 'u-input-number',
31 iconStyle: 'double',
32 autoWidth: false,
33 delay: 300,
34 toNumber:false,
35 displayCheckPrompt:false,
36 locale:{},
37 handleBtnClick:()=>{}
38};
39
40
41//校验提示
42function prompt (content) {
43 Message.destroy();
44 Message.create({content: content, color: 'warninglight'});
45}
46
47
48
49/**
50 * 千分符
51 * @param {要转换的数据} num
52 */
53function toThousands(number) {
54 if(number==='')return '';
55 if(number==='0')return '0';
56 let num = (number || 0).toString();
57 let integer = num.split('.')[0];
58 let decimal = num.split('.')[1]||'';
59 let result = '';
60 while (integer.length > 3) {
61 result = ',' + integer.slice(-3) + result;
62 integer = integer.slice(0, integer.length - 3);
63 }
64 if (integer) {
65 result = integer + result ;
66 if(num=='.'||num.indexOf('.')==num.length-1){
67 result = result + '.'+decimal;
68 }else if (decimal){
69 result = result + '.'+decimal;
70 }
71 }
72 if(result[0]=='-'){
73 result = result.replace('-,','-')
74 }
75 return result;
76}
77
78
79function setCaretPosition(ctrl,pos,need) {
80
81 if(ctrl&&need){
82 if(ctrl.setSelectionRange) {
83 ctrl.focus();
84 ctrl.setSelectionRange(pos,pos);
85 // IE8 and below
86 } else if(ctrl.createTextRange) {
87 var range = ctrl.createTextRange();
88 range.collapse(true);
89 range.moveEnd('character', pos);
90 range.moveStart('character', pos);
91 range.select();
92 }
93
94 }
95
96}
97
98
99
100class InputNumber extends Component {
101
102 constructor(props) {
103 super(props);
104 // 初始化状态,加减按钮是否可用,根据当前值判断
105
106 let data = this.judgeValue(props);
107 this.state = {
108 value:data.value,
109 minusDisabled: data.minusDisabled,
110 plusDisabled: data.plusDisabled,
111 showValue:toThousands(data.value)
112 }
113
114 this.timer = null;
115 this.focus = false;
116 this.selectionStart = 0;
117 }
118
119 // unThousands = (number) =>{
120 // if(!number || number === "")return number;
121 // number = number.toString();
122 // return number.replace(new RegExp(this.props.formatSymbol,'g'),'');
123 // // return number.replace(/\,/g,'');
124 // }
125
126 /**
127 * 校验value
128 * @param {*} props
129 * @param {原来的值} oldValue
130 */
131 judgeValue = (props,oldValue)=> {
132 let currentValue;
133 let currentMinusDisabled = false;
134 let currentPlusDisabled = false;
135 let { value,min,max,precision,onChange,displayCheckPrompt } = props;
136 if(props.minusRight){
137 value = value.toString();
138 if(value.indexOf('-')!=-1){//所有位置的负号转到前边
139 value = value.replace('-','');
140 value = '-'+value;
141 }
142 value = Number(value);
143 }
144 if ((value!=undefined)&&(value!=null)) {
145 if(value===''){
146 currentValue='';
147 return {
148 value: '',
149 minusDisabled: false,
150 plusDisabled: false
151 }
152 }else{
153 currentValue = Number(value) ||0;
154 }
155 } //lse if (min&&(value!='')) {//mdd中提出bug
156 //currentValue = min;
157 //}
158 else if(value==='0'||value===0){
159 currentValue = 0;
160 }else{//NaN
161 if(oldValue||(oldValue===0)||(oldValue==='0')){
162 currentValue = oldValue;
163 }else{//value为空
164 return {
165 value: '',
166 minusDisabled: false,
167 plusDisabled: false
168 }
169 }
170 }
171 if(currentValue==-Infinity){
172 return {
173 value: min,
174 minusDisabled: true,
175 plusDisabled: false
176 }
177 }
178 if(currentValue==Infinity){
179 return {
180 value: max,
181 minusDisabled: false,
182 plusDisabled: true
183 }
184 }
185 const local = getComponentLocale(props, this.context, 'InputNumber', () => i18n);
186 if (currentValue <= min) {
187 if(displayCheckPrompt)prompt(local['msgMin']);
188 currentMinusDisabled = true;
189 currentValue=min;
190 }
191 if (currentValue >= max) {
192 if(displayCheckPrompt)prompt(local['msgMax']);
193 currentPlusDisabled = true;
194 currentValue=max;
195 }
196
197 if(props.hasOwnProperty('precision')){
198 // currentValue = Number(currentValue).toFixed(precision);
199 currentValue = this.getPrecision(currentValue);
200 }
201 if(props.minusRight){
202 currentValue = currentValue.toString();
203 if(currentValue.indexOf('-')!=-1){//负号转到后边
204 currentValue = currentValue.replace('-','');
205 currentValue = currentValue+'-';
206 }
207 }
208
209 return {
210 value: currentValue,
211 minusDisabled: currentMinusDisabled,
212 plusDisabled: currentPlusDisabled
213 }
214 }
215
216 componentDidMount(){
217 this.setState({
218 value: this.props.value,
219 showValue:toThousands(this.props.value)
220 });
221 }
222 componentWillReceiveProps(nextProps){
223 if(this.focus){
224 if(nextProps.value==Infinity||nextProps.value==-Infinity){
225
226 }else{
227 this.setState({
228 value: nextProps.value,
229 showValue:toThousands(nextProps.value),
230 });
231 }
232
233 }else{
234 let data = this.judgeValue(nextProps,this.state.value);
235 this.setState({
236 value: data.value,
237 showValue:toThousands(data.value),
238 minusDisabled: data.minusDisabled,
239 plusDisabled: data.plusDisabled
240 });
241 }
242 }
243
244 ComponentWillUnMount() {
245 this.clear();
246 }
247
248 /**
249 * @memberof InputNumber
250 * type 是否要四舍五入(此参数无效,超长不让输入)
251 */
252 numToFixed = (value,fixed,type) => {
253 value = String(value);
254 if(!value && value !== "0")return value;
255 if(!fixed && String(fixed) !== "0")return value;
256 let preIndex = value.indexOf(".");
257 if(value.indexOf(".") === -1)return value;
258 preIndex++;
259 let endIndex = (preIndex+fixed);
260 let precValue = value.substr(preIndex,endIndex)+"0000000000";
261 if(type){
262 return Number(value).toFixed(fixed);
263 }
264 return value.split(".")[0] +"."+ precValue.substr(0,fixed);
265 }
266
267 handleChange = (value) => {
268 let selectionStart = this.input.selectionStart==undefined?this.input.input.selectionStart:this.input.selectionStart;
269 this.selectionStart = selectionStart;
270 const { onChange,toNumber,minusRight} = this.props;
271 if(value===''){
272 onChange && onChange(value);
273 this.setState({
274 value,
275 showValue:''
276 })
277 return;
278 }
279 // value = this.unThousands(value);
280 if(minusRight){
281 if(value.match(/-/g)&&value.match(/-/g).length>1)return
282 }
283 if(isNaN(value)&&(value!=='.')&&(value!=='-'))return;
284 if(value.indexOf(".") !== -1){//小数最大值处理
285 let prec = String(value.split(".")[1]).replace("-","");
286 if(this.props.precision === 0 && (prec ==="" || prec !=""))return;
287 if(this.props.precision && prec.length > this.props.precision)return;
288 if(prec.length > 8)return;
289 }
290 this.setState({
291 value,
292 showValue:toThousands(value),
293 });
294 if(value==='-'){
295 onChange && onChange(value);
296 }else if(value=='.'||value.indexOf('.')==value.length-1){//当输入小数点的时候
297 onChange && onChange(value);
298 }else if(value[value.indexOf('.')+1]==0){//当输入 d.0 的时候,不转换Number
299 onChange && onChange(value);
300 }else{
301 toNumber?onChange && onChange(Number(value)):onChange && onChange(value);
302 }
303 if(this.props.toThousands){
304 let stateShowValue = toThousands(this.state.value);
305 let showValue = toThousands(value)
306 let addNumber = 0;
307 let delNumber = 0;
308 let reg = /[0-9]/
309 for(let i =0;i<selectionStart;i++){
310 if(!reg.test(showValue[i]))addNumber+=1;
311 }
312 for(let j= 0;j<selectionStart;j++){
313 if(stateShowValue[j]){
314 if(!reg.test(stateShowValue[j]))delNumber+=1;
315 }
316 }
317 let position = selectionStart+addNumber-delNumber;
318 setCaretPosition(this.input&&this.input.input,position,true)
319 }
320 }
321
322
323 handleFocus = (value,e) => {
324 this.focus = true;
325 let {onFocus, min, max } = this.props;
326 onFocus && onFocus(this.getPrecision(this.state.value), e);
327 }
328 /**
329 * 恢复科学技术法的问题
330 */
331 getFullNum = (num)=>{
332 //处理非数字
333 if(isNaN(num)){return num};
334
335 //处理不需要转换的数字
336 var str = ''+num;
337 if(!/e/i.test(str)){return num;};
338 let _precision = this.props.precision?this.props.precision:18;
339 return (Number(num)).toFixed(_precision).replace(/\.?0+$/, "");
340 }
341
342 handleBlur = (v,e) => {
343 this.focus = false;
344 const {onBlur,precision,onChange,toNumber,max,min,displayCheckPrompt,minusRight,round } = this.props;
345 const local = getComponentLocale(this.props, this.context, 'InputNumber', () => i18n);
346 v = this.state.value;//在onBlur的时候不需要活输入框的只,而是要获取state中的值,因为有format的时候就会有问题。
347 if(v==='' || !v){
348 this.setState({
349 value:v
350 })
351 onChange && onChange(v);
352 onBlur && onBlur(v,e);
353 return;
354 }
355 // let value = this.unThousands(v);
356 let value = this.numToFixed(v,precision,round);
357 if(minusRight){
358 if(value.indexOf('-')!=-1){//所有位置的负号转到前边
359 value = value.replace('-','');
360 value = '-'+value;
361 }
362 }
363 value = isNaN(Number(value)) ? 0 : Number(value);
364 if(value>max){
365 if(displayCheckPrompt)prompt(local['msgMax']);
366 value = max;
367 }
368 if(value<min){
369 if(displayCheckPrompt)prompt(local['msgMin']);
370 value = min;
371 }
372 if(this.props.hasOwnProperty('precision')){
373 // value = value.toFixed(precision);
374 value = this.getPrecision(value);
375 }
376 value = value.toString();
377 if(minusRight&&(value.indexOf('-')!=-1)){//负号转到后边
378 value = value.replace('-','');
379 value = value+'-';
380 }
381 this.setState({
382 value,
383 showValue:toThousands(value)
384 });
385 this.detailDisable(value);
386 if(toNumber&&(!minusRight)){
387 onChange && onChange(value);
388 onBlur && onBlur(value,e);
389 }else{
390 onChange && onChange(value);
391 onBlur && onBlur(value,e);
392 }
393 }
394 /**
395 * 设置增加减少按钮是否可用
396 */
397 detailDisable = (value) => {
398 const { max, min, step } = this.props;
399
400 if(value >= max || Number(value) + Number(step) > max){
401 this.setState({
402 plusDisabled: true
403 })
404 }else{
405 this.setState({
406 plusDisabled: false
407 })
408 }
409 if(value <= min || value -step < min){
410 this.setState({
411 minusDisabled: true
412 })
413 }else{
414 this.setState({
415 minusDisabled: false
416 })
417 }
418
419 }
420 /**
421 * 减法
422 */
423 minus = (value) => {
424 const {min, max, step, onChange, toNumber} = this.props;
425 value = (value === '-') ? 0 : value;
426 if(typeof min === "undefined"){
427 value = this.detail(value, step, 'reduce');
428 }else{
429 if(value < min){
430 value = min;
431 }else{
432 let reducedValue = this.detail(value, step, 'reduce');
433 if(reducedValue >= min){
434 value = reducedValue;
435 }
436 }
437 }
438
439 if(value > max){
440 value = max;
441 }
442
443 this.setState({
444 value,
445 showValue:toThousands(value)
446 },()=>{
447 this.input.input.focus&&this.input.input.focus()
448 });
449 toNumber?onChange && onChange(Number(value)):onChange && onChange(value);
450 this.handleBtnClick('down',value);
451 this.detailDisable(value);
452 }
453 /**
454 * 加法
455 */
456 plus = (value) => {
457 const {max, min, step, onChange, toNumber} = this.props;
458 value = (value === '-') ? 0 : value;
459 if(typeof max === "undefined"){
460 value = this.detail(value, step, 'add');
461 }else{
462 if(value > max){
463 value = max;
464 }else{
465 let addedValue = this.detail(value, step, 'add');
466 if(addedValue <= max){
467 value = addedValue;
468 }
469 }
470 }
471 if(value < min){
472 value = min;
473 }
474 this.setState({
475 value,
476 showValue:toThousands(value)
477 },()=>{
478 this.input.input.focus&&this.input.input.focus()
479 });
480 toNumber?onChange && onChange(Number(value)):onChange && onChange(value);
481 this.handleBtnClick('up',value);
482 this.detailDisable(value);
483 }
484
485
486 detail = (value, step, type) => {
487 let {precision} = this.props;
488
489 let valueFloat = this.separate(value);
490 let stepFloat = this.separate(step);
491
492 let ans;
493 let stepFloatLength = stepFloat.toString().length;
494 let valueFloatLength = valueFloat.toString().length;
495
496 if (typeof precision === 'undefined') {
497 precision = Math.max(stepFloatLength, valueFloatLength);
498 }
499 let coefficient = Math.pow(10, Math.abs(stepFloatLength - valueFloatLength));
500 if (type === 'add') {
501 ans = (value * coefficient + step * coefficient) / coefficient;
502 } else {
503 ans = (value * coefficient - step * coefficient) / coefficient;
504 }
505
506 return ans.toFixed(precision);
507
508 }
509
510 /**
511 * 分离小数和整数
512 * @param value
513 * @returns {*}
514 */
515 separate = (value) => {
516 if(value==null||value==undefined){
517 return ""
518 }else{
519 value = value.toString();
520 if(value.indexOf('.') > -1){
521 return value.split('.')[1];
522 }else{
523 return ""
524 }
525 }
526 }
527
528
529
530 clear = () => {
531 if (this.timer) {
532 clearTimeout(this.timer);
533 }
534 }
535
536 handlePlusMouseDown = (e) => {
537 e.preventDefault && e.preventDefault();
538 let {delay,disabled} = this.props;
539 let {value} = this.state;
540 if(disabled)return;
541 this.plus(value);
542 this.clear();
543 this.timer = setTimeout(() => {
544 this.handlePlusMouseDown(e);
545 }, delay);
546 }
547
548 handleReduceMouseDown = (e) => {
549 e.preventDefault && e.preventDefault();
550 let {delay,disabled} = this.props;
551 let {value} = this.state;
552 if(disabled)return;
553 this.minus(value);
554 this.clear();
555 this.timer = setTimeout(() => {
556 this.handleReduceMouseDown(e);
557 }, delay);
558 }
559
560 getPrecision = (value)=>{
561 if(value==null||value==undefined)return value;
562 if(!value && value === "")return value;
563 value = String(value);
564 value = value.indexOf("e") !== -1?this.getFullNum(value):value;
565 const {precision} = this.props;
566 if(precision === 0)return value;
567 if (precision == undefined || (value.indexOf(".") !== -1 && String(value.split(".")[1]).length === precision)) {
568 return value;
569 }
570 let before = value.substring(0,1),len = value.length,
571 after = value.substring(len-1,len);
572 before = before === "-"?before:"";
573 after = after === "-"?after:"";
574 //是科学计数法,不replace -
575 if(before)value = value.substring(1,len-1);
576 if(after)value = value.substring(0,len-1);
577 // value = value.replace("-",'');
578 let precV = "000000000000000000000000000000000000000000000000000000000000000000000000";
579 if(value.indexOf(".") === -1){
580 precV = precV.substr(0,precision);
581 precV = precV?"."+precV:precV;
582 if((!isNaN(value))&&(value.indexOf('-')!=-1||value.indexOf('+')!=-1)&&(value.indexOf('e')!=-1)){//是科学计数法,不拼接0000000
583
584 }else{
585 value = value + precV;
586 }
587 }
588 return before+Number(value).toFixed(precision)+after;
589 }
590
591 handleBtnClick = (type,value)=>{
592 this.props.handleBtnClick(type,value)
593 }
594
595 render() {
596 const {toThousands,minusRight, max, min, step,disabled, clsPrefix, className, delay, onBlur, onFocus, iconStyle, autoWidth, onChange, format, precision,toNumber, ...others} = this.props;
597 let classes = {
598 [`${clsPrefix}-auto`]: autoWidth,
599 [`${clsPrefix}`]: true,
600 [`${clsPrefix}-lg`]: others.size === "lg",
601 [`${clsPrefix}-sm`]: others.size === "sm",
602 };
603
604 let {value, minusDisabled, plusDisabled, showValue} = this.state;
605 value = precision != null && !this.focus?this.getPrecision(value):value;
606 value = format && !this.focus? format(value) : value;
607 value = String(value).indexOf("e") !== -1?this.getFullNum(value):value;
608 if(minusRight && String(value).indexOf('-')!=-1){
609 value = String(value).replace("-","")+"-";
610 }
611 let disabledCursor = disabled? ' disabled-cursor':'';
612 let disabledCon = disabled? ' disabled-con':'';
613 return (
614 <div className={`${clsPrefix}-out`}>
615 {
616 iconStyle === 'double' ? (
617 <InputGroup className={classnames(className, classes,disabledCon)}>
618 <InputGroup.Addon
619 // onClick={()=>{minusDisabled?'':this.handleBtnClick('down')}}
620 className={(minusDisabled && 'disabled' ) + disabledCursor}
621 onMouseDown={ this.handleReduceMouseDown}
622 onMouseLeave={ this.clear }
623 onMouseUp={ this.clear }>
624 -
625 </InputGroup.Addon>
626 <FormControl
627 {...others}
628 value={toThousands?showValue:value}
629 disabled={disabled}
630 onBlur={ this.handleBlur }
631 onFocus={this.handleFocus}
632 onChange={ this.handleChange }
633 ref={ref=>this.input = ref}
634 />
635 <InputGroup.Addon
636 // onClick={()=>{plusDisabled?'':this.handleBtnClick('up')}}
637 className={(plusDisabled && 'disabled' ) + disabledCursor}
638 onMouseDown={ this.handlePlusMouseDown}
639 onMouseLeave={ this.clear }
640 onMouseUp={ this.clear }>
641 +
642 </InputGroup.Addon>
643 </InputGroup>
644 ) : (
645 <InputGroup
646 className={classnames(className, classes,disabledCon)}
647 simple
648 >
649 <FormControl
650 {...others}
651 value={toThousands?showValue:value}
652 disabled={disabled}
653 onBlur={ this.handleBlur }
654 onFocus={this.handleFocus}
655 onChange={ this.handleChange }
656 ref={ref=>this.input = ref}
657 />
658 <InputGroup.Button>
659 <div className={classnames("icon-group")}>
660 <span
661 // onClick={()=>{plusDisabled?'':this.handleBtnClick('up')}}
662 onMouseDown={ this.handlePlusMouseDown}
663 onMouseLeave={ this.clear }
664 onMouseUp={ this.clear }
665 className={classnames('plus',{'disabled': plusDisabled,'disabled-cursor':disabledCursor})}>
666 <span className="uf uf-arrow-up"/>
667 </span>
668 <span
669 // onClick={()=> minusDisabled?'':this.handleBtnClick('down')}
670 onMouseDown={ this.handleReduceMouseDown}
671 onMouseLeave={ this.clear }
672 onMouseUp={ this.clear }
673 className={classnames("reduce",{'disabled': minusDisabled,'disabled-cursor':disabledCursor})}>
674 <span className=" uf uf-arrow-down"/>
675 </span>
676 </div>
677 </InputGroup.Button>
678 </InputGroup>
679 )
680 }
681 </div>
682 );
683 }
684}
685;
686
687InputNumber.defaultProps = defaultProps;
688InputNumber.propTypes = propTypes;
689InputNumber.contextTypes = {
690 beeLocale: PropTypes.object
691};
692export default InputNumber;