index.jsx 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568
  1. import React,{Component} from 'react';
  2. import classNames from 'classnames';
  3. import config from '@config/index.js';
  4. import style from './index.less';
  5. import {setPosition,deepClone,filterArr,handleEnter,isIE,windowEventHandler,filterDataArr,getIds,getPageCoordinate} from '@utils/tools.js';
  6. import {Notify} from '@commonComp';
  7. import ScrollArea from 'react-scrollbar';
  8. import SearchBox from '@containers/SearchBox'
  9. import $ from 'jquery';
  10. /****
  11. * 标签组合下拉,选中的项目展开
  12. * author:zn@2018-11-21
  13. * 接收参数:
  14. * data:下拉内容
  15. * value:选中成文
  16. * placeholder:灰显文字
  17. * ikey:当前标签的index
  18. * copyType:选中后是否复制本身(仅展开类型使用)
  19. * selecteds:选中选项(仅展开类型使用)
  20. * show:是否显示下拉
  21. * order:成文顺序,0点选顺序,1从上到下从左到右
  22. * isImports:是否高亮显示(仅查体中使用)
  23. * type:所在模块:现病史、查体等
  24. * tagType:标签类型
  25. * id:id
  26. * flag:仅主诉中使用
  27. *
  28. * ***/
  29. class SpreadDrop extends Component{
  30. constructor(props){
  31. super(props);
  32. const {nones,noneOn,noneIds,withOn,exists,nowOn,withs,exclusion,excluName} = deepClone(props.selecteds||[]);
  33. this.state = {
  34. nones:nones||'', //无,字符串拼接
  35. exists:exists||[], //主症状id
  36. excluName:excluName||'',
  37. withs:withs||[], //伴随 id
  38. noneIds:noneIds||[],
  39. noneOn:noneOn||false, //无是否选中
  40. withOn:withOn||false, //伴是否选中
  41. nowOn:nowOn||'', //最近选中“无”还是“伴”
  42. exclusion:exclusion||'', //选中互斥项id
  43. timer:null, //延时,区分单击双击
  44. ban:{}, //放'伴'字段
  45. editable:false, //双击编辑
  46. labelVal:'', //存放标签原有的值--主诉字数限制用
  47. left:'auto',
  48. tmpDom:null
  49. };
  50. this.$div = React.createRef();
  51. this.$list = React.createRef();
  52. this.handleSelect = this.handleSelect.bind(this);
  53. this.clearState = this.clearState.bind(this);
  54. this.handleClear = this.handleClear.bind(this);
  55. this.handleShow = this.handleShow.bind(this);
  56. this.handleConfirm = this.handleConfirm.bind(this);
  57. this.changeToEdit = this.changeToEdit.bind(this);
  58. this.handleBlur = this.handleBlur.bind(this);
  59. this.onChange = this.onChange.bind(this);
  60. /*是否点击确定按钮标记,处理点击其他等同于确定操作bug,
  61. 如不区分确定按钮提交和点击其他提交,确定按钮提交后会多更新一次状态导致数据重复*/
  62. this.btnClickFlag = false;
  63. }
  64. handleShow(e){//单击
  65. e&&e.stopPropagation();
  66. const {ikey,handleShow,placeholder,flag,id,value,tagType,type,data,windowWidth,setHighter} = this.props;
  67. let num = 0;//判断为五类切超出页面
  68. data && data.map((item)=>{
  69. if(item.formPosition != 1){
  70. ++num
  71. }
  72. });
  73. const listWidth = 30+$(this.$list.current).width();
  74. if(num >= 5 && windowWidth-getPageCoordinate(e).boxLeft < listWidth){
  75. this.setState({
  76. left:windowWidth-listWidth-150
  77. })
  78. }
  79. //高度超出时,增加左侧大容器padding
  80. setPosition(e,this.$list.current,setHighter);
  81. // window.event? window.event.cancelBubble = true : e.stopPropagation();
  82. this.setStateInit(); //恢复初始选中状态
  83. const that = this;
  84. this.btnClickFlag = false;
  85. clearTimeout(this.state.timer);
  86. this.state.timer = setTimeout(()=>{
  87. if (that.state.editable) {//如果处于编辑状态点击不显示下拉框
  88. return
  89. }else{
  90. document.activeElement.blur()//chrome41有效,但是失去焦点的span仍能编辑
  91. $(e.target).parent().prev().attr({"contentEditable":false})
  92. this.setState({
  93. tmpDom:e.target
  94. })
  95. handleShow&&handleShow({ikey,placeholder,flag,id,value,tagType,type});
  96. }
  97. },300)
  98. }
  99. changeToEdit(e){//双击
  100. window.getSelection ? window.getSelection().removeAllRanges() : document.selection.empty();
  101. const {value,id,placeholder,handleDbclick,handleHide} = this.props;
  102. let text = e.target.innerText || e.target.innerHTML;
  103. handleHide&&handleHide(); //展开情况下双击收起
  104. // clearTimeout(this.state.timer);//取消延时的单击事件
  105. e.stopPropagation();
  106. // e.preventDefault();
  107. if(value&&value.trim()){//有选中值的标签才能双击编辑
  108. this.setState({
  109. labelVal:text,
  110. editable:true
  111. });
  112. //失焦关闭编辑状态
  113. setTimeout(()=>{
  114. e.target.focus();
  115. })
  116. handleDbclick && handleDbclick({value,id,placeholder});
  117. }
  118. }
  119. onChange(e){
  120. const {mainSaveText,ikey,type,handleLabelChange} = this.props;
  121. const {labelVal,editable} = this.state;
  122. let mainText = filterDataArr(mainSaveText);//主诉字数
  123. if(editable){//避免IE中点击标签也会触发
  124. let val = e.target.innerText || e.target.innerHTML;
  125. if(+type==1){// 主诉字数达到上限时不允许输入
  126. if(mainText.length >= config.limited){
  127. if(val.length > labelVal.length){
  128. e.target.innerText?(e.target.innerText = labelVal):(e.target.innerHTML = labelVal);
  129. Notify.info(config.limitText);
  130. return
  131. }else if(val.length == labelVal.length){
  132. this.setState({
  133. labelVal:val
  134. });
  135. }else{
  136. handleLabelChange && handleLabelChange({ikey,changeVal:val,type});
  137. }
  138. }
  139. }
  140. }
  141. }
  142. handleBlur(e){
  143. e.stopPropagation();
  144. const {ikey,type,handleLabelChange} = this.props;
  145. const {editable} = this.state;
  146. const ev = e || window.event;
  147. if(editable){
  148. // 更改标签的value值
  149. let changeVal = ev.target.innerText || ev.target.innerHTML;
  150. if(!isIE()){
  151. e.target.innerText?(e.target.innerText = ''):(e.target.innerHTML = ''); //避免出现重复输入值
  152. }
  153. handleLabelChange && handleLabelChange({ikey,changeVal,type});
  154. }
  155. this.setState({
  156. editable:false
  157. });
  158. }
  159. setStateInit(){
  160. const {nones,noneOn,noneIds,withOn,exists,nowOn,withs,exclusion,excluName,ban} = deepClone(this.props.selecteds||[]);
  161. this.setState({
  162. nones:nones||'',
  163. exists:exists||[],
  164. excluName:excluName||'',
  165. withs:withs||[],
  166. noneIds:noneIds||[],
  167. noneOn:noneOn||false,
  168. withOn:withOn||false,
  169. nowOn:nowOn||'',
  170. exclusion:exclusion||'',
  171. ban:ban||{},
  172. });
  173. }
  174. clearState(){
  175. this.setState({
  176. nones:'',
  177. exists:[],
  178. //existsName:{},
  179. //withsName:{},
  180. withs:[],
  181. noneIds:[],
  182. noneOn:false,
  183. withOn:false,
  184. nowOn:'',
  185. exclusion:'',
  186. excluName:'',
  187. ban:{}
  188. });
  189. this.btnClickFlag = false;
  190. }
  191. handleClear(e){
  192. e.stopPropagation();
  193. this.clearState();
  194. }
  195. handleConfirm(e){
  196. // e.stopPropagation();
  197. const {handleConfirm,ikey,type,tagType,order,mainSaveText,copyType,value,mainData} = this.props;
  198. const params = Object.assign({},this.state,{ikey,type,tagType,order,mainSaveText,copyType,value,mainData});
  199. delete params.tmpDom; //避免上面deepClone selecteds报错
  200. handleConfirm&&handleConfirm(params);
  201. this.btnClickFlag = true;
  202. //点确定后隐藏弹窗
  203. this.props.handleHide();
  204. }
  205. handleSelect(item,isExclu,joint,listIndex,selected){//console.log(item,selected)
  206. let {withOn,withs,noneOn,exclusion,exists,nowOn,nones,noneIds,ban} = this.state;
  207. /*if(this.props.selecteds)
  208. console.log(exists,this.props.selecteds.exists,exists===this.props.selecteds.exists)*/
  209. const id = item.id;
  210. const linkStr = joint||'';
  211. const name = item.name+linkStr;
  212. if(isExclu){ //操作“互斥项”
  213. if([...noneIds,...exists,...withs].length>0){ //已选非互斥项,清空已选项,选中该互斥项
  214. this.clearState();
  215. this.setState({
  216. exclusion:id,
  217. excluName:name
  218. });
  219. return;
  220. }
  221. //未选中互斥项,直接选中该互斥项
  222. let temp = '';
  223. if(exclusion==''){
  224. temp = id;
  225. }else if(exclusion!=''&&exclusion!==id){
  226. temp = exclusion;
  227. }
  228. this.setState({
  229. exclusion:temp,
  230. excluName:temp ===''?'':name
  231. });
  232. return;
  233. }
  234. //操作单选项
  235. if(selected){
  236. const tIndex= exists.findIndex((it)=>it.questionId===item.questionId);
  237. const bIndex= withs.findIndex((it)=>it.questionId===item.questionId);
  238. if(tIndex!=-1){
  239. exists.splice(tIndex,1,Object.assign({},item,{name})); //修改单选列连接字符不显示bug
  240. this.setState({
  241. exists,
  242. })
  243. }
  244. if(bIndex!=-1){
  245. withs.splice(tIndex,1,item);
  246. this.setState({
  247. withs,
  248. })
  249. }
  250. return;
  251. }
  252. if(exclusion!==''){ //互斥项已选中,清空互斥项
  253. this.setState({
  254. exclusion:'',
  255. excluName:''
  256. });
  257. }
  258. if(+item.code===1){ //操作“伴”类型
  259. this.setState({
  260. withOn:!withOn,
  261. // withs:withOn?[]:[...withs,id], //取消“伴”选中,伴随症状全部取消选中
  262. // withs:withOn?[]:[...withs,{id:item.id,name:name}],
  263. withs:withOn?[]:withs,
  264. ban:withOn?{}:{id:id,name:name,value:name},
  265. //withsName:withOn?"":withsName+name, //取消“伴”选中,伴随症状全部取消选中
  266. nowOn:withOn?(noneOn?'none':''):'with'
  267. });
  268. return;
  269. }
  270. if(+item.code===2){ //操作“无”类型
  271. this.setState({
  272. noneOn:!noneOn,
  273. noneIds:noneOn?[]:[...noneIds,id],
  274. nones:noneOn?'':name,
  275. nowOn:noneOn?(withOn?'with':''):'none'
  276. });
  277. return;
  278. }
  279. //操作普通项
  280. let existsIds = exists.length>0? getIds(exists):[];
  281. let withsIds = withs.length>0? getIds(withs):[];
  282. if(existsIds.includes(id)){
  283. let existsData = exists;
  284. exists.forEach((it,i)=>{
  285. if(it.id==id){
  286. existsData.splice(i,1);
  287. }
  288. })
  289. exists = existsData;
  290. }else if(noneIds.includes(id)){
  291. nones = nones.replace(name+'、','');
  292. noneIds.splice(noneIds.indexOf(id),1);
  293. }else if(withsIds.includes(id)){
  294. let withsData = withs;
  295. withs.forEach((it,i)=>{
  296. if(it.id==id){
  297. withsData.splice(i,1);
  298. }
  299. })
  300. withs = withsData;
  301. }else{ //选中普通项
  302. if(nowOn=='none'){
  303. nones += name+'、';
  304. noneIds.push(id);
  305. }else if(nowOn=='with'){
  306. // withs.push({id:id,name:name,questionId:item.questionId});
  307. withs.push({id:id,name:name,questionId:item.questionId,conceptId:item.conceptId});
  308. }else{
  309. // exists.push({id:id,name:name,listIndex,questionId:item.questionId});
  310. exists.push({id:id,name:name,listIndex,questionId:item.questionId,conceptId:item.conceptId});
  311. }
  312. }
  313. this.setState({
  314. nones,
  315. noneIds,
  316. exists,
  317. withs,
  318. ban
  319. //existsName,
  320. //withsName,
  321. });
  322. }
  323. getClass(){
  324. const {isImports,show,value,isExtBlue} = this.props;
  325. const blueBorder = this.state.editable?style['blue-border']:'';
  326. const orgBorder = isImports&&!value?style['orange-border']:'';
  327. const ext = isExtBlue?style['ext']:'';
  328. if(show){
  329. $(this.$div.current).addClass(style['borderd']);
  330. }else{
  331. $(this.$div.current).removeClass(style['borderd']);
  332. }
  333. if(value){
  334. return classNames(style['selected-tag'],blueBorder);
  335. }
  336. return classNames(style['tag'],orgBorder,ext);
  337. }
  338. componentDidMount(){
  339. if(isIE()){
  340. $(this.$div.current).onIe8Input(function(e){
  341. this.onChange(e)
  342. },this);
  343. }
  344. }
  345. /*componentWillReceiveProps(nextProps){
  346. const {setHighter} = this.props;
  347. if(!nextProps.show){console.log(22)
  348. setHighter&&setHighter(48);
  349. }
  350. }*/
  351. render(){
  352. const {placeholder,value,show,data,order,type,tagType,ikey} = this.props;
  353. const {tmpDom,left} = this.state
  354. const clickIndx = ikey.split('-')[1];//展开下拉的index
  355. if(!show&&tmpDom){
  356. $(tmpDom).parent().prev().attr({"contentEditable":true})
  357. }
  358. const {editable} = this.state;
  359. return <div className={style['container']}
  360. onFocus={(e)=>e.stopPropagation()}
  361. onBlur={(e)=>e.stopPropagation()}
  362. onInput={(e)=>e.stopPropagation()}>
  363. <div
  364. ref={this.$div}
  365. onClick={this.handleShow}
  366. className={this.getClass()}
  367. contentEditable={editable}
  368. onDoubleClick={this.changeToEdit}
  369. onBlur={this.handleBlur}
  370. onInput={this.onChange}
  371. onkeydown={handleEnter}
  372. >{value||placeholder}</div>
  373. <ListItems parDiv={this.$list} data={data} order={order} left={left} boxMark={type} tagType={tagType}
  374. show={show} cliIndex={clickIndx} handleSelect={this.handleSelect} handleConfirm={this.handleConfirm} handleClear={this.handleClear} {...this.state}></ListItems>
  375. </div>
  376. }
  377. }
  378. class ListItems extends Component{
  379. constructor(props){
  380. super(props);
  381. this.$cont = React.createRef();
  382. this.clickConfirm = this.clickConfirm.bind(this);
  383. }
  384. getLabels(){
  385. const {data,handleSelect} = this.props;
  386. let detail = [];
  387. let isSpecialPos = false; //是否特殊位置(单行在上面,如无殊)
  388. let isExclu = false; //是否与其他互斥
  389. let isRadio; //是否为单选列,默认多选列
  390. let isSingle = data.length == 1?true:false; //是否为单列,无伴的情况
  391. const list = data&&data.map((it,i)=>{
  392. isSpecialPos = (+it.formPosition === 1);
  393. isExclu = (+it.exclusionType===1);
  394. isRadio = (+it.tagType ===1&&+it.controlType === 1);
  395. if(+it.controlType===0){
  396. detail = it.questionMapping;
  397. }else{
  398. detail = it.questionDetailList;
  399. }
  400. return <ListItem datas={detail}
  401. isRadio={isRadio}
  402. isSingle={isSingle}
  403. joint={it.joint}
  404. listIndex={i}
  405. isSpecialPos={isSpecialPos}
  406. isExclu={isExclu}
  407. handleClick={handleSelect}
  408. {...this.props}></ListItem>;
  409. });
  410. return list;
  411. }
  412. clickConfirm(){
  413. const {handleConfirm} = this.props;
  414. this.child&&this.child.reset();//重置搜索框中的数据
  415. handleConfirm();
  416. }
  417. getStyle(){
  418. const {show,left} = this.props;
  419. return {
  420. display:show?'block':'none',
  421. left:left
  422. }
  423. }
  424. render (){
  425. const {handleClear,handleConfirm,order,parDiv,boxMark,tagType,show,cliIndex} = this.props;
  426. return <div className={style["drop-list"]} ref={parDiv} style={this.getStyle()} contentEditable="false" onClick={(e)=>{e.stopPropagation();}}>
  427. <p className={style['orderTips']}>按{order?'从左到右从上到下':'点击'}顺序成文</p>
  428. {this.getLabels()}
  429. <div className="oper clearfix">
  430. <span className={style['clear']} onClick={handleClear}>清空选项</span>
  431. <span className={style['confirm']} onClick={this.clickConfirm}>确定</span>
  432. </div>
  433. {boxMark==1 && tagType==11 && <div className="search">
  434. <div className={style["line"]}></div>
  435. <SearchBox show={show} cliIndex={cliIndex} onSelect={handleConfirm} onRef={(child)=>{this.child = child;}}/>
  436. </div>}
  437. </div>
  438. }
  439. }
  440. class ListItem extends Component{
  441. constructor(props){
  442. super(props);
  443. this.getData = this.getData.bind(this);
  444. }
  445. handleClick(e,item,i){
  446. e.stopPropagation();
  447. // window.event? window.event.cancelBubble = true : e.stopPropagation();
  448. const {handleClick,isExclu,isRadio,data,exists,noneIds,withs,joint,listIndex} = this.props;
  449. const index=listIndex+''+i;
  450. //列单选处理
  451. if(isRadio){
  452. let selected = exists.find((i)=>{
  453. return i.questionId===item.questionId;
  454. })||withs.find((i)=>{
  455. return i.questionId===item.questionId;
  456. })||noneIds.find((i)=>{
  457. return i.id===item.questionId;
  458. });
  459. /*const selected = data.find((it)=>{console.log(exists)
  460. return exists.findIndex((i)=>i.questionId===it.id)!==-1||noneIds.includes(it.id)||withs.findIndex((i)=>i.questionId===it.id)!==-1;
  461. });*/
  462. if(selected&&selected.id!=item.id){ //该列已有选中项,传回已选中的id,name取消选中
  463. handleClick&&handleClick(item,isExclu,joint,index,{id:selected.id,name:selected.name,questionId:selected.questionId});
  464. }else{
  465. handleClick&&handleClick(item,isExclu,joint,index);
  466. }
  467. return;
  468. }
  469. handleClick&&handleClick(item,isExclu,joint,index);
  470. }
  471. getClass(id){ //无之后显示黑色,之前显示蓝色
  472. const {exclusion,nones,noneIds,exists,withs,isExclu,ban} = this.props;
  473. if(exclusion!=''){
  474. if(+id===+exclusion){
  475. return style['selected'];
  476. }else{
  477. return style['exclusion'];
  478. }
  479. }else{
  480. if(isExclu&&([...noneIds,...exists,...withs].length>0||ban.id)){
  481. return style['exclusion'];
  482. }
  483. if(noneIds.includes(id)){
  484. return style['none-selected'];
  485. }
  486. let existsIds = getIds(exists);
  487. let withsIds = getIds(withs);
  488. // if(existsIds.includes(id)||withsIds.includes(id)){
  489. if(existsIds.includes(id)||withsIds.includes(id)||ban.id && ban.id==id){
  490. return style['selected'];
  491. }
  492. return '';
  493. }
  494. }
  495. getData(){
  496. const {datas,isSpecialPos} = this.props;
  497. const contStyle={
  498. opacity:'0.4',
  499. right:'0',
  500. top:'1px',
  501. zIndex:'15',
  502. width:'6px',
  503. background:'#f1f1f1'};
  504. const barStyle={background:'#777',width:'100%'};
  505. if(datas&&datas.length>11){
  506. return <ScrollArea speed={0.8}
  507. horizontal={false}
  508. stopScrollPropagation={true}
  509. style={{maxHeight:'330px'}}
  510. className={style["area"]}
  511. verticalContainerStyle={contStyle}
  512. verticalScrollbarStyle={barStyle}
  513. contentClassName="content">
  514. {datas&&datas.map((it,i)=>{
  515. if(isSpecialPos){
  516. return <li onClick={(e)=>this.handleClick(e,it,i)} className={this.getClass(it.id)}>{it.name}</li>
  517. }
  518. return <li onClick={(e)=>this.handleClick(e,it,i)} className={this.getClass(it.id)} title={it.name.length>8?it.name:''}>{it.name&&it.name.length>8?it.name.slice(0,8)+'...':it.name}</li>
  519. })}
  520. </ScrollArea>;
  521. }
  522. return datas&&datas.map((it,i)=>{
  523. if(isSpecialPos){
  524. return <li onClick={(e)=>this.handleClick(e,it,i)} className={this.getClass(it.id)}>{it.name}</li>
  525. }
  526. return <li onClick={(e)=>this.handleClick(e,it,i)} className={this.getClass(it.id)} title={it.name.length>8?it.name:''}>{it.name&&it.name.length>8?it.name.slice(0,8)+'...':it.name}</li>
  527. });
  528. }
  529. getMainData(){//主诉添加症状-带搜索框
  530. const {datas,isSpecialPos} = this.props;
  531. return datas&&datas.map((it,i)=>{
  532. return <li onClick={(e)=>this.handleClick(e,it,i)}
  533. className={this.getClass(it.id)}
  534. title={it.name.length>4?it.name:''}
  535. style={{'width':'55px','display':'inline-block'}}>
  536. {it.name&&it.name.length>4?it.name.slice(0,4)+'...':it.name}
  537. </li>
  538. });
  539. }
  540. render(){
  541. const {datas,isSpecialPos,boxMark,tagType,listIndex,isSingle} = this.props;
  542. const pos = isSpecialPos?style['independent']:'';
  543. const ifMainSear = boxMark==1 && tagType==11?true:false;
  544. // 单列直接设置宽度,多列则第二列设置(伴字ul不设置宽度)
  545. const main = ifMainSear&&!isSingle&&listIndex==1?style['mainUl']:(ifMainSear&&isSingle?style['mainUl']:'');
  546. return <ul className={classNames(style['row'],pos,main)} onBlur={(e)=>e.stopPropagation()}>
  547. {ifMainSear?this.getMainData():this.getData()}
  548. </ul>
  549. }
  550. }
  551. export default SpreadDrop;