定制化开发——时间轴组件

一、需求

1、开发一个展示轨迹数据用的时间轴组件;
2、后端传入是一个 list,数据格式如下:

const dataRalation = [
	  {
	    entityID : "111",	// 主体id
	    accompanyID : "222",		// 伴随id
	    entityTime : "2019-08-05 22:26:34",	时间
	    accompanyTime : "2019-08-05 22:33:31",		// 伴随时间
	    entitySpace : "未知之地" ,		// 地点
	    accompanySpace : "未知之地" ,		// 伴随地点
	    entiyImg : "no_photo.png" ,		// 图片
	    accompanyImg : "no_photo.png" ,		// 伴随图片
	  },
	  ......
  ]

3、点击日期,其对应的下列数据可以收起
成果如下图:
在这里插入图片描述

&
在这里插入图片描述

二、思路

1、根据时间将传入的 list 数据转化为map。
2、提取日期作为map的键,剩下数据以数组的形式作为map键值对的值存储。
3、遍历map,根据map的数据来生成组件

三、代码

3.1、第一版代码

这是我当时写的第一版代码,其中有个bug。就是测试的时候发现一点击日期所有的展示数据都收起来了。导致的原因是控制展示的 displaytype是遍历生成的。故,组件中的的this.state.displaytype,指代了所有小组件的displaytype。当触发clickTitle方法时,所有的displaytype全部改变,没有实现第三点需求。

import * as React from 'react';
import {format} from 'date-fns';
import PerfectScrollbar from 'perfect-scrollbar';
import {Text} from '../../common/text';
import { IconFont } from '../../common/iconFont';

export interface IProps{
  // 关系对象数据
  data : any,
  // 左边加载的图片
  leftImg : string,
  // 右边加载的图片
  rightImg : string,
  // 颜色数组
  color : string[],
  // 主体对象类型
  objectType : string,
  // 关联对象类型
  relationType : string
}

// IListType[]
export interface IListType {
  objectaddress : string
  objectId : string
  objectTime : string
  unionaddress : string
  unionId : string
  unionTime : string
  entiyImg : string
  accompanyImg : string
}

export class TimeShaft extends React.PureComponent<IProps,any>{

  private dom: React.RefObject<HTMLDivElement> ;
  private scroller: PerfectScrollbar | undefined ;

  constructor(props:IProps){
    super(props);
    this.dom = React.createRef();
    this.state = {
      displayType : 'flex' ,
      objectType : this.props.objectType ,
      relationType : this.props.relationType
    }
  }

  public componentWillReceiveProps(nextProps:IProps){

    this.setState({
      objectType : nextProps.objectType,
      relationType: nextProps.relationType,
    });
    
  }

  // 组件渲染之后设置滚动条样式
  public componentDidMount() {
    const wrapper = this.dom.current;
    if (wrapper) {
      this.scroller = new PerfectScrollbar(wrapper, {
          wheelPropagation: false,
          wheelSpeed: 2,
      });
      this.updateScroller();
    }
  }
  // 组件更新后设置滚动条样式
  public componentDidUpdate() {
    this.updateScroller();
  }
  public updateScroller() {
    this.scroller!.update();
  }

  public render() {

    const html : any = [] ;
    
    const {displayType,objectType,relationType} = this.state;
    const {data,leftImg,rightImg,color} = this.props;
    // 定义一个map对象
    const map : Map<string,Array<[]>> = new Map<string,Array<[]>>();
    // 将data组装成map
    data.forEach((item:any, i:number) => {
      // 取时间的年月日 dateTitle,作为map的键
      const dateTitle = format(item.entityTime, 'YYYY-MM-DD');
      // 如果map的键值对中没有 dateTitle,则set一个并创建一个空数组存储值
      if(!map.has(dateTitle)){
        map.set(dateTitle,[]);
      }
      // 如果map中已有该键值对,往数组中push遍历的对象item
      const list = map.get(dateTitle);
      if(list){
        list.push(item);
      }
    });

    // 先生成htmlbody
    map.forEach((list, value) => {
      const datalist : IListType[] = []
      // htmlbody要写在foreach循环里面,如果写全局变量最后会把全部结果push进去
      const htmlbody : any = [] ;

      list.forEach((item:any, num :number) => {
        datalist.push({
          objectaddress : item.entitySpace,
          objectId : item.entityID ,
          objectTime : item.entityTime,
          unionaddress : item.accompanySpace,
          unionId : item.accompanyID,
          unionTime : item.accompanyTime,

          entiyImg : item.entiyImg ,
          accompanyImg : item.accompanyImg ,
        })

        htmlbody.push(
          <div style={{display:displayType, width:'100%'}}>
            {/* 左 */}
            <div className="shaft-htmlbody-main" style={{ justifyContent:'flex-end'}}>
              <div className="shaft-htmlbody-dif" style={{ marginRight:'10px' }}>
                <div className="shaft-htmlbody-border" style={{ border:'1px solid' + color[0] }}>

                  <div className="shaft-htmlbody-smallTitle" style={{backgroundColor:color[0]}}>
                    <img className="shaft-htmlbody-img" src={leftImg} alt=""/>
                    <div className="shaft-htmlbody-font">
                      { objectType  === '01' 
                        ? <Text widths="120px" data={datalist[num].objectId.slice(0,7)}/> 
                        : <Text widths="120px" data={datalist[num].objectId}/> }
                    </div>
                  </div>

                  <div className="shaft-htmlbody-dataImg">
                    <img style={{ width:'100%' }} src={datalist[num].entiyImg}/>
                  </div>

                  <div className="shaft-htmlbody-time">
                    <div style={{width:'40%'}}>过卡时间:</div>
                    <Text widths="60%" data={format(datalist[num].objectTime, 'HH:mm:ss')}/>
                  </div>

                  <div className="shaft-htmlbody-space">
                    <div style={{width:"40%"}}>卡口地点:</div>
                    <Text widths="60%" data={datalist[num].objectaddress}/>
                  </div>

                </div>
              </div>
            </div>
            
            {/* 中间icon */}
            <div style={{width: 2, display:'flex', flexDirection:'column'}}>
              <div style={{width: 2, backgroundColor: '#e6e6e6',height:30}}>{}</div>
              <div style={{width: 2, height:32}}>
                <IconFont extraCommonProps={ {style:{fontSize:30,color:'#b3b3b3',marginLeft:-15}} } type="iconshijian2" />
              </div>
              <div style={{width: 2, backgroundColor: '#e6e6e6', flex:'1'}}>{}</div>
            </div>

            {/* 右 */}
            <div className="shaft-htmlbody-main" style={{ justifyContent:'flex-start'}}>
              <div className="shaft-htmlbody-dif" style={{marginLeft:'10px'}}>
                <div className="shaft-htmlbody-border" style={{ border:'1px solid'+color[1] }}>

                  <div className="shaft-htmlbody-smallTitle" style={{ backgroundColor:color[1] }}>
                    <img className="shaft-htmlbody-img" src={rightImg} alt=""/>
                    <div className="shaft-htmlbody-font">
                      { relationType  === '01' 
                        ? <Text widths="120px" data={datalist[num].unionId.slice(0,7)}/> 
                        : <Text widths="120px" data={datalist[num].unionId}/> }
                    </div>
                  </div>

                  <div className="shaft-htmlbody-dataImg">
                    <img style={{ width:'100%' }} src={datalist[num].accompanyImg}/>
                  </div>

                  <div className="shaft-htmlbody-time">
                    <div style={{width:"40%"}}>过卡时间:</div>
                    <Text widths="60%" data={format(datalist[num].unionTime, 'HH:mm:ss')}/>
                  </div>

                  <div className="shaft-htmlbody-space">
                    <div style={{width:'40%'}}>卡口地点:</div>
                    <Text widths="60%" data={datalist[num].unionaddress}/>
                  </div>

                </div>
              </div>
            </div>

          </div>
        )
      })
      // 按value值循环生成htmlbody后,再生成html模块
      html.push(
        <div style={{width:'100%'}}>
          <div style={{width:'100%', display:'flex', justifyContent:'center'}}>
            <div className="timeline-head" onClick={this.clickTitle(value)}>{value}</div>
          </div>
          {htmlbody}
          <div style={{height:'5px'}}>{}</div>
        </div>
      )
    })

    return(

      <div style={{flex:1,width:'100%',position:'relative',padding:'15px'}} ref={this.dom}>
        {html}
      </div>
    )
  }

  private clickTitle = (value:any) => {

    return () => {
      if (this.state.displayType === 'flex') {
        this.setState({
          displayType : 'none'
        })
      }else{
        this.setState({
          displayType : 'flex'
        })
      }
    }
  }

}

3.2、改进

改进思路:

当时思考了一下,认为有三种改进方法:
1、将时间title和展示数据的小模块提成一个小组件,这样小组件中的this.state.displaytype则可以分别代表对应日期数据的展示状态。
2、在生成map的时候可以加一个标记元素,当这个标记元素为ture则展示数据,为false不展示。 private clickTitle = (value:any) => {}方法根       据value来改变不同日期的标记元素。
3、dom操作,先获取到当前点击的div,再获到当前div的下级div,(即为该日期对应的展示数据)将其设为隐藏

改进说明:

我是按思路1改的。因为这种模式比较契合数据驱动ui的设计思路。另外两种方式,我觉得理论可行但未予以实践。如所述有错欢迎大家积极留言指正,如有什么疑问也欢迎各位同学留言讨论,我会尽量帮助你们弄明白。

改进后代码:

timelinebody.tsx为提取出来的小组件,timeline时间轴组件遍历map的时候生成timelinebody组件。这样每天展示数据的displaytype都是遍历生成的,可以达到点击日期,收起其对应的日期的数据的目的

时间轴:timeLine.tsx

import * as React from 'react';
import {format} from 'date-fns';
import PerfectScrollbar from 'perfect-scrollbar';
import { TimeLineBody } from './timeLineBody';

export interface IProps{
  data : any,
  leftImg : string,
  rightImg : string,
  color : string[],
  iconUrl : string,
  objectType : string,
  relationType : string
}

export interface IListType {
  objectaddress : string
  objectId : string
  objectTime : string
  unionaddress : string
  unionId : string
  unionTime : string

  entiyImg : string
  accompanyImg : string
}

export class TimeShaft extends React.PureComponent<IProps,any>{

  private dom: React.RefObject<HTMLDivElement> ;
  private scroller: PerfectScrollbar | undefined ;

  constructor(props:IProps){
    super(props);
    this.dom = React.createRef();
    this.state = {
      displayType : 'flex' ,
      objectType : this.props.objectType ,
      relationType : this.props.relationType
    }
  }

  public componentWillReceiveProps(nextProps:IProps){

    this.setState({
      relationId : nextProps.objectType,
      relationType: nextProps.relationType,
    });
    
  }

  // 组件渲染之后设置滚动条样式
  public componentDidMount() {
    const wrapper = this.dom.current;
    if (wrapper) {
      this.scroller = new PerfectScrollbar(wrapper, {
          wheelPropagation: false,
          wheelSpeed: 2,
      });
      this.updateScroller();
    }
  }
  // 组件更新后设置滚动条样式
  public componentDidUpdate() {
    this.updateScroller();
  }
  public updateScroller() {
    this.scroller!.update();
  }

  public render() {
    
    const { objectType, relationType } = this.state;
    const { data, leftImg, rightImg, color } = this.props;
    const body : any = [];
    // 定义一个map对象
    const map : Map<string,Array<[]>> = new Map<string,Array<[]>>();
    // 将data组装成map
    data.forEach((item:any, i:number) => {
      // 取时间的年月日 dateTitle,作为map的键
      const dateTitle = format(item.entityTime, 'YYYY-MM-DD');
      // 如果map的键值对中没有 dateTitle,则set一个并创建一个空数组存储值
      if(!map.has(dateTitle)){
        map.set(dateTitle,[]);
      }
      // 如果map中已有该键值对,往数组中push遍历的对象item
      const list = map.get(dateTitle);
      if(list){
        list.push(item);
      }
    });

    map.forEach((list, value) => {

      body.push(
        <TimeLineBody
          leftImg = {leftImg}
          rightImg = {rightImg}
          color = {color}
          objectType = {objectType}
          relationType = {relationType}
          list = {list}
          value = {value}
        />
      )
 
    })

    return(
      <div style={{flex:1,width:'100%',position:'relative'}} ref={this.dom}>
        {body}
      </div>
    )
  }

}

提取出来的小组件:timeLineBody.tsx

import * as React from 'react';
import {format} from 'date-fns';
import PerfectScrollbar from 'perfect-scrollbar';
import {Text} from '../../common/text';
import { IconFont } from '../../common/iconFont';

export interface IProps{
  leftImg : string,
  rightImg : string,
  color : string[],
  objectType : string,
  relationType : string,

  list : any,
  value : any,
}

export interface IListType {
  objectaddress : string
  objectId : string
  objectTime : string
  unionaddress : string
  unionId : string
  unionTime : string

  entiyImg : string
  accompanyImg : string
}

export class TimeLineBody extends React.PureComponent<IProps,any>{

  constructor(props:IProps){
    super(props);
    this.state = {
      displayType : 'flex' ,
      objectType : this.props.objectType ,
      relationType : this.props.relationType
    }
  }

  public componentWillReceiveProps(nextProps:IProps){

    this.setState({
      objectType : nextProps.objectType,
      relationType: nextProps.relationType,
    });
    
  }

  public render() {

    const { displayType,objectType,relationType } = this.state;
    const { leftImg,rightImg,color,list,value } = this.props;

    const html : any = [] ;
    const datalist : IListType[] = [] ;
    // htmlbody要写在foreach循环里面,如果写全局变量最后会把全部结果push进去
    const htmlbody : any = [] ;

    list.forEach((item:any, num :number) => {
      datalist.push({
        objectaddress : item.entitySpace,
        objectId : item.entityID ,
        objectTime : item.entityTime,
        unionaddress : item.accompanySpace,
        unionId : item.accompanyID,
        unionTime : item.accompanyTime,

        entiyImg : item.entiyImg ,
        accompanyImg : item.accompanyImg ,
      })

      htmlbody.push(
        <div style={{display:displayType, width:'100%'}}>
          {/* 左 */}
          <div className="shaft-htmlbody-main" style={{ justifyContent:'flex-end'}}>
            <div className="shaft-htmlbody-dif" style={{ marginRight:'10px' }}>
              <div className="shaft-htmlbody-border" style={{ border:'1px solid' + color[0] }}>

                <div className="shaft-htmlbody-smallTitle" style={{backgroundColor:color[0]}}>
                  <img className="shaft-htmlbody-img" src={leftImg} alt=""/>
                  <div className="shaft-htmlbody-font">
                    { objectType  === '01' 
                      ? <Text widths="120px" data={datalist[num].objectId.slice(0,7)}/> 
                      : <Text widths="120px" data={datalist[num].objectId}/> }
                  </div>
                </div>

                <div className="shaft-htmlbody-dataImg">
                  <img style={{ width:'100%' }} src={datalist[num].entiyImg}/>
                </div>

                <div className="shaft-htmlbody-time">
                  <div style={{width:'40%'}}>过卡时间:</div>
                  <Text widths="60%" data={format(datalist[num].objectTime, 'HH:mm:ss')}/>
                </div>

                <div className="shaft-htmlbody-space">
                  <div style={{width:"40%"}}>卡口地点:</div>
                  <Text widths="60%" data={datalist[num].objectaddress}/>
                </div>

              </div>
            </div>
          </div>
          
          {/* 中间icon */}
          <div style={{width: 2, display:'flex', flexDirection:'column'}}>
            <div style={{width: 2, backgroundColor: '#e6e6e6',height:30}}>{}</div>
            <div style={{width: 2, height:32}}>
              <IconFont extraCommonProps={ {style:{fontSize:30,color:'#b3b3b3',marginLeft:-15}} } type="iconshijian2" />
            </div>
            <div style={{width: 2, backgroundColor: '#e6e6e6', flex:'1'}}>{}</div>
          </div>

          {/* 右 */}
          <div className="shaft-htmlbody-main" style={{ justifyContent:'flex-start'}}>
            <div className="shaft-htmlbody-dif" style={{marginLeft:'10px'}}>
              <div className="shaft-htmlbody-border" style={{ border:'1px solid'+color[1] }}>

                <div className="shaft-htmlbody-smallTitle" style={{ backgroundColor:color[1] }}>
                  <img className="shaft-htmlbody-img" src={rightImg} alt=""/>
                  <div className="shaft-htmlbody-font">
                    { relationType  === '01' 
                      ? <Text widths="120px" data={datalist[num].unionId.slice(0,7)}/> 
                      : <Text widths="120px" data={datalist[num].unionId}/> }
                  </div>
                </div>

                <div className="shaft-htmlbody-dataImg">
                  <img style={{ width:'100%' }} src={datalist[num].accompanyImg}/>
                </div>

                <div className="shaft-htmlbody-time">
                  <div style={{width:"40%"}}>过卡时间:</div>
                  <Text widths="60%" data={format(datalist[num].unionTime, 'HH:mm:ss')}/>
                </div>

                <div className="shaft-htmlbody-space">
                  <div style={{width:'40%'}}>卡口地点:</div>
                  <Text widths="60%" data={datalist[num].unionaddress}/>
                </div>

              </div>
            </div>
          </div>

        </div>
      )
    })
    // 按value值循环生成htmlbody后,再生成html模块
    html.push(
      <div style={{width:'100%'}}>

        <div style={{width:'100%', display:'flex', justifyContent:'center'}}>
          <div className="timeline-head" onClick={this.clickTitle(value)}>{value}</div>
        </div>

        {htmlbody}

        <div style={{height:'5px'}}>{}</div>

      </div>
    )
   

    return(
      <div style={{flex:1,width:'100%',position:'relative',padding:'15px'}} >
        {html}
      </div>
    )
  }

  private clickTitle = (value:any) => {

    return () => {
      if (this.state.displayType === 'flex') {
        this.setState({
          displayType : 'none'
        })
      }else{
        this.setState({
          displayType : 'flex'
        })
      }
    }
  }

}
原创文章 19 获赞 16 访问量 2447

猜你喜欢

转载自blog.csdn.net/qq_42778289/article/details/102782296