index.jsx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  1. import React,{Component} from 'react';
  2. import className from 'classnames';
  3. import {NumberPan,Notify} from '@commonComp';
  4. import style from './index.less';
  5. import {setFontColorSize,handleMouseUp} from '@utils/tools'
  6. /***
  7. * author:zn@2018-11-19
  8. * 接收参数:
  9. * value: 默认选中的值
  10. * placeholder:灰显文字
  11. * handleSelect: 选中事件
  12. * show: 是否显示下拉
  13. * allClick:是否前后缀也可唤出数字键盘
  14. *
  15. * ***/
  16. class NumberDrop extends Component{
  17. constructor(props){
  18. super(props);
  19. this.state={
  20. /*editable:false, //标签是否可输入*/
  21. value:props.value||'',
  22. timer:null,
  23. sltTimer:null,
  24. blurTimer:null,
  25. hasSelect:false, //是否点过下拉键盘
  26. placeholder:props.placeholder
  27. };
  28. this.$span = React.createRef();
  29. this.numInpBlur = this.numInpBlur.bind(this);
  30. this.handleSpanInp = this.handleSpanInp.bind(this);
  31. this.handleNumClick = this.handleNumClick.bind(this);
  32. this.handleNumFocus = this.handleNumFocus.bind(this);
  33. this.handleKeyDowm = this.handleKeyDowm.bind(this);
  34. this.beyondArea = this.beyondArea.bind(this);
  35. this.emitBMIData = this.emitBMIData.bind(this);
  36. }
  37. select(text){ //选中键盘上数字事件
  38. //placeholder修改后,第一次点击键盘触发blur后onClick不触发,原因未知,改为onMouseup可触发
  39. let timer = null;
  40. clearTimeout(this.state.sltTimer);
  41. clearTimeout(this.state.blurTimer);
  42. const {handleSelect,ikey,suffix,prefix,mainSaveText,min,max,formulaCode} = this.props;
  43. const needCompare=min!=undefined&&max!=undefined;
  44. if(!text){
  45. this.setState({
  46. placeholder:this.props.placeholder
  47. });
  48. }else{
  49. if(needCompare){
  50. const that = this;
  51. const isFine = this.validSymbols(text,min,max); //有~或/时是否合理
  52. const hasSymbol = /[\/|\~]/g.test(text); //是否有~或/
  53. const singleFine = !isNaN(+text)&&parseFloat(min)<=parseFloat(text)&&parseFloat(text)<=parseFloat(max); //无~或/时是否合理
  54. timer = setTimeout(function(){
  55. clearTimeout(that.state.sltTimer);
  56. if(text!=''&&(!hasSymbol&&!singleFine)||(hasSymbol&&!isFine)){
  57. that.beyondArea();
  58. return;
  59. }
  60. },1500);
  61. }
  62. this.setState({
  63. hasSelect:true,
  64. sltTimer:timer
  65. });
  66. }
  67. handleSelect&&handleSelect({ikey,text,suffix,prefix,mainSaveText,formulaCode});
  68. //BMI相关数据上传
  69. this.emitBMIData(text);
  70. }
  71. emitBMIData(text){
  72. //手动修改BMI相关值时数据上传
  73. const {setBMIParam,formulaCode,setBMI} = this.props;
  74. if(formulaCode==="BMI_RES"){
  75. setBMI&&setBMI(text);
  76. }
  77. if(text!==0&&(formulaCode==="BMI_SG"||formulaCode==="BMI_TZ")){
  78. const map={
  79. BMI_SG:'tall',
  80. BMI_TZ:'weight'
  81. };
  82. setTimeout(function(){
  83. setBMIParam&&setBMIParam(map[formulaCode],text);
  84. });
  85. }
  86. }
  87. beyondArea(){
  88. const {handleSelect,ikey,suffix,prefix,mainSaveText,formulaCode} = this.props;
  89. Notify.info("输入数值不符合规范,请重新输入!");
  90. handleSelect&&handleSelect({ikey,text:'',suffix,prefix,mainSaveText,formulaCode});
  91. this.emitBMIData('');
  92. this.setState({
  93. placeholder:this.props.placeholder,
  94. hasSelect:false
  95. });
  96. }
  97. handleNumFocus(e){
  98. /*const {placeholder} = this.state;
  99. const val = e.target.innerText.trim();
  100. //console.log(33,e.target.innerText,placeholder,e.target.innerText.trim() == placeholder)
  101. if(val!=''&&val == placeholder){
  102. this.setState({
  103. placeholder:''
  104. });
  105. }*/
  106. e.stopPropagation();
  107. }
  108. handleKeyDowm(e){
  109. if(e.keyCode==13){
  110. const {reFocus,num,handleHide} = this.props;
  111. reFocus&&reFocus(num);
  112. handleHide && handleHide();
  113. }
  114. }
  115. handleNumClick(e){ //数字框不可编辑的状态时点击事件,点击将数字框变为可输入且下拉不再显示直到失焦后再次聚集
  116. e.stopPropagation();
  117. const {show,handleShow,ikey,id,patId,handleHide} = this.props;
  118. if(show) {
  119. handleHide && handleHide();
  120. return;
  121. }else{
  122. this.$span.current.focus();
  123. this.setState({
  124. hasSelect:false,
  125. placeholder:'' //火狐26placeholder点击不隐藏bug修改
  126. });
  127. handleShow&&handleShow({ikey,id:patId||id});
  128. }
  129. }
  130. validSymbols(txt,min,max){
  131. //输入只有一个~或/时判断两边是否为合理数字,有多个为不合理
  132. const index1 = txt.indexOf('~');
  133. const index2 = txt.indexOf('/');
  134. const needCompare = min!=undefined&&max!=undefined;
  135. let arr1=[],arr2=[];
  136. if(index1!=-1&&index1==txt.lastIndexOf('~')&&index1!=txt.length-1){ //有且只有一个~,且不在最后
  137. arr1 = txt.split('~');
  138. //~的范围在合理范围内为合理值
  139. if(!isNaN(+arr1[0])&&!isNaN(+arr1[1])&&((!needCompare)||(needCompare&&parseFloat(min)<=parseFloat(arr1[0])&&parseFloat(arr1[0])<=parseFloat(max)&&parseFloat(min)<=parseFloat(arr1[1])&&parseFloat(arr1[1])<=parseFloat(max)))){
  140. return true
  141. }
  142. return false;
  143. }
  144. if(index2!=-1&&index2==txt.lastIndexOf('/')&&index2!=txt.length-1){ //有且只有一个~,且不在最后
  145. arr2 = txt.split('/');
  146. // /两边的数字分别在合理范围内为合理值
  147. if(!isNaN(+arr2[0])&&!isNaN(+arr2[1])&&((!needCompare)||(needCompare&&parseFloat(min)<=parseFloat(arr2[0])&&parseFloat(arr2[0])<=parseFloat(max)&&parseFloat(min)<=parseFloat(arr2[1])&&parseFloat(arr2[1])<=parseFloat(max)))){
  148. return true
  149. }
  150. return false;
  151. }
  152. return false;
  153. }
  154. numInpBlur(e){ //数字框失焦,保存值到store中
  155. e.stopPropagation();
  156. const {handleSelect,ikey,suffix,prefix,mainSaveText,min,max,show,formulaCode} = this.props;
  157. /*if(show){ //修改清空后第一次点击键盘不触发click事件bug--失焦placehoder消失,弃用
  158. return;
  159. }*/
  160. //输入超出合理范围或输入不是数字提示且清空
  161. const needCompare=min!=undefined&&max!=undefined;
  162. const txt = e.target.innerHTML.replace(/&nbsp;$|^&nbsp;/,'');//e.target.innerText.trim();
  163. const isFine = this.validSymbols(txt,min,max); //有~或/时是否合理
  164. const hasSymbol = /[\/|\~]/g.test(txt); //是否有~或/
  165. const singleFine = (!isNaN(+txt)&&!needCompare)||(!isNaN(+txt)&&needCompare&&parseFloat(min)<=parseFloat(txt)&&parseFloat(txt)<=parseFloat(max)); //无~或/时是否合理
  166. if(txt!=''&&(!hasSymbol&&!singleFine)||(hasSymbol&&!isFine)){
  167. this.beyondArea();
  168. return;
  169. }
  170. //输入为空时显示placeholder
  171. const timer = setTimeout(()=>{
  172. if(!e.target.innerHTML.replace(/&nbsp;$|^&nbsp;/,'')){
  173. this.setState({
  174. placeholder:this.props.placeholder
  175. });
  176. }
  177. },200);
  178. this.setState({
  179. blurTimer:timer
  180. });
  181. const val = e.target.innerHTML.replace(/&nbsp;$|^&nbsp;/,'');//e.target.innerText.trim();
  182. const {placeholder} = this.state;
  183. let text = val===placeholder?'':val;
  184. //e.target.innerText = ''; //避免出现重复输入值
  185. handleSelect&&handleSelect({ikey,text,suffix,prefix,mainSaveText,formulaCode});
  186. this.emitBMIData(text);
  187. }
  188. handleSpanInp(e){ //数字框输入事件
  189. e.stopPropagation();
  190. const txt = e.target.innerHTML.replace(/&nbsp;$|^&nbsp;/,'');
  191. this.setState({
  192. value:txt
  193. });
  194. const {handleHide} = this.props;
  195. handleHide&&handleHide();
  196. }
  197. getClasses(nospecial){ //整个标签是否有值的状态
  198. const {hideTag,placeholder,isImports,isExtBlue,isSelectAll,mouseSelect} = this.props;
  199. const val = this.state.value;
  200. const isSelected = val&&val!=placeholder?style['selected']:style['container'];
  201. const orgBorder = isImports&&!(val&&val!=placeholder)?style['orange-border']:'';
  202. const ext = isExtBlue?style['ext']:'';
  203. const noTag = hideTag?style['no-tag']:'';
  204. const selectedArea = mouseSelect?style['selected-area']:'';
  205. return className(isSelected,noTag,orgBorder,ext,setFontColorSize(val||isSelectAll?'2,6':isExtBlue?2:1),selectedArea);
  206. }
  207. getSpanClass(nospecial){ //将被替换的文字选中状态显示
  208. const {isSelectAll,isExtBlue,show,mouseSelect} = this.props;
  209. const val = this.state.value;
  210. const selectedArea = mouseSelect?style['selected-area']:'';
  211. const cls = this.props.show?style['blued']:style['nol'];
  212. // return cls+' '+setFontColorSize(nospecial?'':2);
  213. return className(show?style['blued']:(style['nol'],setFontColorSize(val?'2,6':isSelectAll?'2,6':isExtBlue?2:1)),selectedArea)
  214. }
  215. stopBubble(e){
  216. e.stopPropagation();
  217. }
  218. handleMouseDown(){
  219. const {i,setSelectArea,boxMark}= this.props;
  220. setSelectArea({i,boxMark,dir:'start'});
  221. }
  222. componentDidMount(){
  223. //设置最小宽度避免输入后宽度跳动
  224. const spanWidth = window.getComputedStyle(this.$span.current).width;
  225. this.$span.current.style.minWidth=spanWidth;
  226. //保存输入框dom以便聚焦
  227. const that = this;
  228. setTimeout(function(){ //多个其他史/现病史bug修改
  229. const {saveDoms} = that.props;
  230. saveDoms&&saveDoms(that.$span);
  231. })
  232. }
  233. componentWillReceiveProps(nextProps){
  234. //BMI存值
  235. const {formulaCode,bmi,suffix,prefix,ikey,handleSelect,mainSaveText,wrBmi} = this.props;
  236. if(formulaCode==="BMI_RES"&&nextProps.bmi!==bmi){
  237. handleSelect&&handleSelect({ikey,text:nextProps.bmi,suffix,prefix,mainSaveText,formulaCode});
  238. }
  239. //手动修改BMI时身高体重清空
  240. if(wrBmi!==nextProps.wrBmi&&(formulaCode==="BMI_SG"||formulaCode==="BMI_TZ")){
  241. handleSelect&&handleSelect({ikey,text:'',suffix,prefix,mainSaveText,formulaCode});
  242. }
  243. if((nextProps.placeholder == this.props.placeholder)&&(nextProps.value == this.props.value)){
  244. return
  245. }
  246. this.setState({
  247. placeholder:nextProps.placeholder,
  248. value:nextProps.value
  249. });
  250. }
  251. render(){
  252. const {prefix,suffix,show,value,handleHide,allClick,bmi,formulaCode,nospecial,isSelectAll,isExtBlue,select_start,i,boxMark} = this.props;
  253. const val = formulaCode==="BMI_RES"?(+bmi===Infinity||isNaN(bmi)?'':bmi):value;
  254. const {placeholder,hasSelect} = this.state;
  255. return <div className={this.getClasses(nospecial)}
  256. style={{position:'relative'}}
  257. onClick={allClick?this.handleNumClick:null}
  258. onMouseUp={()=>handleMouseUp({select_start,i,boxMark})}
  259. onMouseDown={this.handleMouseDown.bind(this)}>
  260. <span className={`${setFontColorSize(val?'':isSelectAll?2:isExtBlue?2:1)} prefixUnset`}>{prefix}</span>
  261. <span onFocus={this.handleNumFocus}
  262. onClick={allClick?null:this.handleNumClick}
  263. contentEditable={true}
  264. style={{minWidth:'10px',display:'inline-block',textAlign:'center'}}
  265. ref = {this.$span}
  266. onkeyup={this.handleKeyDowm}
  267. onBlur={this.numInpBlur}
  268. onInput={this.handleSpanInp}
  269. className={`${this.getSpanClass(nospecial)}`}
  270. >&nbsp;{val||placeholder}</span>
  271. <span className={`${setFontColorSize(val?'':isSelectAll?2:isExtBlue?2:1)} prefixUnset`}>{suffix}</span>
  272. <NumberPan handleSelect={this.select.bind(this)}
  273. noString={formulaCode?true:false}
  274. onClose={handleHide}
  275. value={this.state.value}
  276. show={show}
  277. toClear={!hasSelect}/>
  278. </div>
  279. }
  280. }
  281. export default NumberDrop;