index.jsx 17 KB

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