欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-04-28 16:35:57
...

一、需求

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的键,剩下数据仍以list的形式作为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'
        })
      }
    }
  }

}