# Timeline 时间轴

- 来源：[E10 UI 组件库](https://www.e-cology.com.cn/ui/timeline/freepass)
- 平台：PC
- 维护者：李戈峰
- 设计稿：http://pd.e-cology.com.cn/pd/E10/interface/e10xiaoguotu/PC/E10zjys/#s37
- Issue：http://gitlab.ecology10.cn/FRONTEND/weapp-ui/-/issues?state=all&search=[Timeline]

## 代码演示

### 基础用法

基础的时间轴，垂直展示时间流信息。

```jsx
import { Component } from "react";
import { Timeline, TimelineItemProps, Icon } from "@weapp/ui";

const datas: Array<TimelineItemProps> = [
  {
    rightChildren: () => '订单创建 2022-6-18',
  },
  {
    rightChildren: () => '订单确认 2022-7-18'
  },
  {
    rightChildren: () => '订单发货 2022-8-18',
  },
  {
    rightChildren: () => '订单签收 2022-9-18',
  },
]

export default class TimelineDemo extends Component {
  render() {
    return (
      <Timeline
        datas={datas}
        dotStyle={{ background: '#fff', border: '1px solid skyblue' }}
      >
      </Timeline >
    )
  }
}
```

### 自定义dot样式

可通过 dotStyle 设置节点样式,可以通过 dot 自定义时间节点,可以设置为图标或者其他内容,可设置dotBottom设置节点底部内容.

```jsx
import { Component } from "react";
import { Timeline, TimelineItemProps, Avatar, Icon } from "@weapp/ui";
import imgSrc from "../../../../assets/images/ODTLcjxAfvqbxHnVXCYX.png"

const dotStyle = {
  width: '10px',
  height: '10px',
  backgroundColor: "#fff",
}

const datas: Array<TimelineItemProps> = [
  {
    rightChildren: () => '订单创建 2022-6-18',
    dotStyle: {
      ...dotStyle,
      border: '1px solid #1890ff'
    },
  },
  {
    rightChildren: () => '订单确认 2022-7-18',
    dotStyle: {
      ...dotStyle,
      border: '1px solid #52c41a'
    },
  },
  {
    rightChildren: () => '订单付款 2022-8-18',
    dotStyle: {
      ...dotStyle,
      border: '1px solid #f5222d'
    },
  },
  {
    rightChildren: () => '订单出库 2022-9-18',
    dotStyle: {
      ...dotStyle,
      border: '1px solid #bbb'
    },
    dotBottom: () => <Icon weId={'x_hda435'} name={'Icon-Business-Use-of-Private-Cars-mcolor'} />,
  },
  {
    rightChildren: () => '订单派送 2022-10-18',
    dot: () => <Avatar size='large' url={imgSrc}></Avatar>,
  },
  {
    rightChildren: () => '订单签收 2022-10-18',
    dotStyle: { backgroundColor: "#FFF" },
    dot: () => <Icon moduleColor={'blue'} name={'Icon-correct02'} />,
    dotBottom: () => <Icon weId={'sdfsdss'} name={'Icon-space02-mcolor'} />
  },
]

export default class TimelineDemo extends Component {

  render() {
    return (
      <Timeline
        datas={datas}
      >
      </Timeline>
    )
  }
}
```

### 交替展示

可通过 data 的 rightChildren 和 leftChildren,自定义渲染位置.

```jsx
import { Component } from "react";
import { Timeline, TimelineItemProps, Icon } from "@weapp/ui";

const dotStyle = {
  width: 20,
  height: 20,
  backgroundColor: "#fff",
}
const datas: Array<TimelineItemProps> = [
  {
    rightChildren: () => '订单创建 2022-6-18',
    dotStyle: { ...dotStyle, border: '1px solid #1890ff' },
  },
  {
    leftChildren: () => '已确认',
    dotStyle: dotStyle,
    dot: () => <Icon moduleColor={'green'} name={'Icon-correct02'} />,

  },
  {
    rightChildren: () => '订单发货 2022-8-18',
    dotStyle: dotStyle,
    dot: () => <Icon moduleColor={'blue'} name={'Icon-custom50-o'} />

  },
  {
    leftChildren: () => '已签收',
    dotStyle: dotStyle,
    dot: () => <Icon weId={'x_hda435'} name={'Icon-Signed-for-mcolor'} />,

  },
]

export default class TimelineDemo extends Component {

  render() {
    return (
      <div style={{ display: 'flex', justifyContent: 'center' }}>
        <Timeline
          datas={datas}
        >
      </Timeline>
      </div >

    )
  }
}
```

### 自定义混合展示

根据需求在时间轴两侧渲染其他组件,设置时间节点间的间隔.

```jsx
import { Component } from "react";
import { Timeline, TimelineItemProps, Input, Icon, Avatar } from "@weapp/ui";
import imgSrc from "../../../../assets/images/ODTLcjxAfvqbxHnVXCYX.png"
const datas: Array<TimelineItemProps> = [
  {
    leftChildren:function(){return 'test'},
    dotStyle:{
      width: '50px',
      height: '50px',
      border: 'none'
    },
    dot:function() {
    return (<Avatar size='large' url={imgSrc}></Avatar>)
    },
    rightChildren: () => 'test'
  },
  {
    leftChildren:function() {
      return <Input placeholder="Input" />},
    dotStyle:{
      width: '10px',
      height: '10px',
    },
    rightChildren: () => 'test'
  },
  {
    leftChildren:() => 'test2',
    dot: function(){
      return <Icon moduleColor={'blue'} name={'Icon-correct02'} />
    },
    rightChildren: () => 'test'
  },
  {

    leftChildren:() => 'test3',
    dotStyle:{
      width: '50px',
      height: '50px',
      border: 'none'
    },
    dot: function(){
      return <Icon moduleColor={'blue'} name={'Icon-correct02'} />
    },
    rightChildren: () => {
    return <Input style={{ width: 200, height: 200 }} placeholder="Disabled Input" disabled />
    }
  },
  {
    dot:() => '测试',
    dotStyle:{
      width: '50px',
      height: '50px',
      backgroundColor: '#ccc',
      color: '#fff'
    },
    dotBottom:() => '测试',
    leftChildren:() => 'test',
    middleStyle:{ width: '40px', minHeight: '160px' },
    rightChildren: () => {
      return <Input style={{ width: 200 }} placeholder="Disabled Input" disabled />
    }
  },
  {
    leftChildren:() => 'test',
    rightChildren: () => {
      return <Input style={{ width: 200 }} placeholder="Disabled Input" disabled />
    }

  }
]

export default class TimelineDemo extends Component {
  render() {
    return (
      <Timeline
      // leftStyle={{width:'40px'}}
      middleStyle={{width:'40px', minHeight: '80px'}}
      datas={datas}
      >
      </Timeline>
    )
  }
}
```

### 微博模块

微博模块使用.

```jsx
import React from 'react';
import { Icon, Timeline, TimelineItemProps } from '@weapp/ui';

const datas: Array<TimelineItemProps> = [
  {
    rightChildren: () => '2021年3月',
  },
  {
    leftChildren: function () { return '已提交' },
    dotStyle: {
      width: '24px',
      height: '24px',
      border: 'none',
      background: '#52C41A'
    },
    dot: function () {
      return (<Icon style={{color: "#fff"}} name="Icon-complete" />)
    },
    dotBottom:() => '今天',
    rightChildren: () => <div style={{width: 300, height: 200}}>Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words, consectetur, from a Lorem Ipsum passage, and going through the cites of the word in classical literature, discovered the undoubtable source. Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of "de Finibus Bonorum et Malorum" (The Extremes of Good and Evil) by Cicero, written in 45 BC. This book is a treatise on the theory of ethics, very popular during the Renaissance. The first line of Lorem Ipsum, "Lorem ipsum dolor sit amet..", comes from a line in section 1.10.32.</div>
  },
  {
    leftChildren: function () { return '已补交' },
    dotStyle: {
      width: '24px',
      height: '24px',
      border: 'none',
      background: '#FAAD14'
    },
    dot: function () {
      return (<Icon style={{color: "#fff"}} name="Icon-time-o" />)
    },
    dotBottom:() => '昨天',
  },
  {
    leftChildren: function () { return '未提交' },
    dotStyle: {
      width: '24px',
      height: '24px',
      border: 'none',
      background: '#FF4D4F '
    },
    dot: function () {
      return (<Icon style={{color: "#fff"}} name="Icon-remind04" />)
    },
    dotBottom:() => '6日',
  },
  {
    dotStyle: {
      width: '24px',
      height: '24px',
      border: 'none',
      background: '#52C41A'
    },
    dot: function () {
      return (<Icon style={{color: "#fff"}} name="Icon-complete" />)
    },
    dotBottom:() => '5日',
  },
]

const TimelineDemo = () => {
  return (
    <div>

      <Timeline
        leftStyle={{width:'50px',paddingTop: '6px'}}
        // middleStyle={{width:'40px', minHeight: '80px'}}
        datas={datas}
      />
    </div>
  );
}

export default TimelineDemo;
```

### 日报

日报中使用.

```jsx
import React from 'react';
import { Icon, Timeline, TimelineItemProps } from '@weapp/ui';

const otherStyle = {
  width: '38px',
  height: '38px',
  border: '1px solid #5D9CEC',
  color: '#5D9CEC',
  background: '#fff',
  fontSize: '12px'
}

const datas: Array<TimelineItemProps> = [
  {
    dotStyle: {
      background: '#fff',
      border: '1px solid #E5E5E5'
    },
    rightChildren: () => '2021年4月',
  },
  {
    dotStyle: {
      width: '38px',
      height: '38px',
      border: 'none',
      color: '#fff',
      background: '#5D9CEC',
      fontSize: '12px'
    },
    dot: function () {
      return '今天';
    },
  },
  {
    dotStyle: otherStyle,
    dot: () => '昨天',
    rightChildren: () => <div style={{ height: 200, width: 300 }}>Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words, consectetur, from a Lorem Ipsum passage, and going through the cites of the word in classical literature, discovered the undoubtable source. Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of "de Finibus Bonorum et Malorum" (The Extremes of Good and Evil) by Cicero, written in 45 BC. This book is a treatise on the theory of ethics, very popular during the Renaissance. The first line of Lorem Ipsum, "Lorem ipsum dolor sit amet..", comes from a line in section 1.10.32.</div>
  },
  {
    dotStyle: otherStyle,
    dot: () => '6日',
  },
  {
    dotStyle: otherStyle,
    dot: () => '5日',
  },
]

const TimelineDemo = () => {
  return (
    <div className='timeline-daily'>

      <Timeline
        leftStyle={{ width: '50px', paddingTop: '6px' }}
        // middleStyle={{width:'40px', minHeight: '80px'}}
        datas={datas}
        tailColor={"#5D9CEC"}
      />
    </div>
  );
}

export default TimelineDemo;
```

### 项目

其他项目中使用.

```jsx
import React from 'react';
import { Avatar, Icon, Timeline, TimelineItemProps } from '@weapp/ui';

const projectItem = (position?: any) => (
  <>
    {/* <p>2021/04/25</p> */}
    <Avatar size='large' />
  </>
)

const time = (<span style={{color: '#999'}}>08:01</span>)

const datas: Array<TimelineItemProps> = [
  {
    dotStyle: {
      width: '42px',
      height: '42px',
    },
    dotTop: () => (<div className="dotTop">2021/04/24</div>),
    dot: function () {
      return projectItem();
    },
    dotBottom: function() {
      return time;
    },
    rightChildren: () => <div style={{height: 100}}></div>

  },
  {
    dotStyle: {
      width: '42px',
      height: '42px',
    },
    dot: function () {
      return projectItem();
    },
    dotBottom: function() {
      return time;
    },
    rightChildren: () => <div style={{height: 100}}></div>
  },
  {
    dotStyle: {
      width: '42px',
      height: '42px',
    },
    dotTop: () => (<div className="dotTop">2021/04/24</div>),
    dot: function () {
      return projectItem();
    },
    dotBottom: function() {
      return time;
    },
    rightChildren: () => <div style={{height: 100}}></div>
  },
  {
    dotStyle: {
      width: '42px',
      height: '42px',
    },
    dot: function () {
      return projectItem();
    },
    dotBottom: function() {
      return time;
    }
  },
]

const TimelineDemo = () => {
  return (
    <div style={{paddingTop: 50}}>

      <Timeline
        leftStyle={{ width: '50px', paddingTop: '6px' }}
        // middleStyle={{width:'40px', minHeight: '80px'}}
        datas={datas}
      />
    </div>
  );
}

export default TimelineDemo;
```

### 报告模块

报告模块中使用.

```jsx
import React, { Component } from 'react';
import { Avatar, Icon, Input, Select, SelectOptionData, SelectValueType, SingleType, Timeline, TimelineItemProps } from "@weapp/ui";

interface IDot {
  [k: string]: any;
}

const dotStyle = {
  display: 'block',
  width: '100%',
  height: '100%',
  borderRadius: "50%"
}
const seasonDotStyle = {
  width: '16px',
  height: '16px',
  border: 'none',
  background: '#D5D5D5 100%',
  borderRadius: "50%"
};
const monthDotStyle = {
  width: '10px',
  height: '10px',
  border: 'none',
  background: '#D5D5D5 100%',
  borderRadius: "50%"
};
const weekDotStyle = {
  width: '8px',
  height: '8px',
  border: 'none',
};

const MonthText = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十', '十一', '十二'];

const options = [
  { id: '1', content: '2021', },
  { id: '2', content: '2022', },
];
class TimelineDemo extends Component {
  state = {
    value: options[0],
    datas: [],
  }

  componentDidMount() {
    const datas = this.generateCalendar(this.state.value.content)
    this.setState({ datas })
  }

  generateCalendar = (year: string) => {
    var arrData = [];
    var getFirstMondayEachMonth = function (string: string) {
      var now = new Date(string);
      var day = now.getDay();
      var firstDate = null;
      if (day !== 1) {
        // get first day of first week of year
        firstDate = now.setDate(now.getDate() + 8 - day)
      } else {
        firstDate = now;
      }
      return new Date(firstDate);
    }

    for (var i = 0; i < 12; i++) {
      var monthItemDot: IDot = {
        leftChildren: () => { },
        dotStyle: monthDotStyle,
        dot: () => { },
      };
      monthItemDot.type = 'month';
      monthItemDot.leftChildren = ((inner, monthIndex) => {
        return () => (<span className="demo-timeline-month" onClick={this.monthClick(monthIndex)}>{MonthText[inner]}月</span>);
      })(i, arrData.length)
      monthItemDot.dot = ((monthIndex) => {
        return () => (<span style={dotStyle} onClick={this.monthClick(monthIndex)}></span>);
      })(arrData.length);

      // add Month
      arrData.push(monthItemDot);
      monthItemDot.weekRange = [];

      // generate week
      for (var j = 0; j < 5; j++) {
        var weekItemDot: IDot = {
          dotStyle: weekDotStyle,
          dot: null,
        }
        var firstDate = getFirstMondayEachMonth(`${year}-${i + 1}`);
        var sWeek = new Date(new Date(firstDate).setDate(new Date(firstDate).getDate() + 7 * j));
        var endWeek = new Date(new Date(sWeek).setDate(new Date(sWeek).getDate() + 6));
        if (i == sWeek.getMonth()) {
          //console.log(sWeek, i, sWeek.getMonth());

          // add Week
          const [month, day] = [sWeek.getMonth(), sWeek.getDate()];
          const [emonth, eday] = [endWeek.getMonth(), endWeek.getDate()];

          weekItemDot.type = 'week';
          weekItemDot.itemClassName = 'hide';
          weekItemDot.text = `${month + 1}-${day}~${emonth + 1}-${eday}`;
          weekItemDot.dot = ((weekIndex) => {
            return () => {
              return (<span style={dotStyle} onClick={this.weekClick(weekIndex)}></span>)
            };
          })(arrData.length);
          weekItemDot.rightChildren = ((inner, text, weekIndex) => {
            return () => (<span
              className="demo-timeline-week"
              onClick={this.weekClick(weekIndex)}>
              第{inner + 1}周<br />
              <span className="demo-timeline-week-tip">{text}</span>
            </span>);
          })(j, `${month + 1}-${day}~${emonth + 1}-${eday}`, arrData.length);

          monthItemDot.weekRange.push(arrData.length);
          arrData.push(weekItemDot);
        }

      }

      // add season
      const season: IDot = [2, 5, 8, 11];
      const seasonText: IDot = { 2: '第一季度', 5: '年中', 8: '第三季度', 11: `${year}年度` };
      if (season.includes(i)) {
        var seasonItemDot: any = {
          leftChildren: () => { },
          dotStyle: seasonDotStyle,
          dot: () => { },

        };
        seasonItemDot.type = 'season';
        seasonItemDot.leftChildren = ((inner, seasonIndex) => {
          return () => (<span className="demo-timeline-month" onClick={this.monthClick(seasonIndex)}>{seasonText[inner.toString()]}</span>);
        })(i, arrData.length)
        seasonItemDot.dot = ((seasonIndex) => {
          return () => (<span style={dotStyle} onClick={this.monthClick(seasonIndex)}></span>);
        })(arrData.length);

        // add season
        arrData.push(seasonItemDot)
      }
    }
    return arrData;
  }

  monthClick = (monthIndex: number) => {
    // 这种写法有性能问题，需要注意
    return () => {
      this.lastWeek = -1;
      const datas: IDot = this.state.datas;
      for (let i = 0; i < datas.length; i++) {
        if (datas[i].type === 'week') {
          datas[i].itemClassName = 'hide';
        }
        if (datas[i].type === 'month' || datas[i].type === 'season') {
          datas[i].itemClassName = '';
        }
      }
      datas[monthIndex].itemClassName = 'active';

      // 遍历当前月份包含的周数
      datas[monthIndex].weekRange?.forEach((element: any) => {
        datas[element].itemClassName = '';
      });

      this.setState({ datas });
    }
  }

  lastWeek = -1;
  weekClick = (week: number) => {
    return () => {
      const datas: IDot = this.state.datas;
      for (let i = 0; i < datas.length; i++) {
        if (datas[i].type === 'month' || datas[i].type === 'season') {
          datas[i].itemClassName = '';
        }
      }
      if (this.lastWeek > -1) {
        datas[this.lastWeek].itemClassName = '';
      }
      datas[week].itemClassName = 'active';
      this.setState({ datas });
      this.lastWeek = week;
    }
  }

  onClick = () => {
    console.log(1)
  }

  handleChange = (v: SelectValueType, option: any, event: any) => {
    const datas = this.generateCalendar(option.content)
    this.setState({ value: v as string, datas })
  };

  render() {
    return (
      <div className="timeline-report">
        <Select
          value={this.state.value}
          style={{ marginBottom: 20, width: 200, }}
          data={options}
          onChange={this.handleChange}
        />
        <Timeline
          leftStyle={{ width: 70, textAlign: "right" }}
          datas={this.state.datas}
        />
      </div>
    );
  }
}

export default TimelineDemo;
```

### 分页展示

分页配置api 保持一致

```jsx
import { Component } from "react";
import { Timeline, TimelineItemProps, Input, Icon, Avatar, PaginationProps, PaginationType } from "@weapp/ui";
import imgSrc from "../../../../assets/images/ODTLcjxAfvqbxHnVXCYX.png"

const datas: Array<TimelineItemProps> = [
  {
    leftChildren:function(){return 'test'},
    dotStyle:{
      width: '50px',
      height: '50px',
      border: 'none'
    },
    dot: function() {
      return (<Avatar size='large' url={imgSrc}></Avatar>)
    },
    rightChildren: () => 'test'
  },
  {
    leftChildren:function() {
      return <Input placeholder="Input" />},
    dotStyle:{
      width: '10px',
      height: '10px',
    },
    rightChildren: () => 'test'
  },
  {
    leftChildren:() => 'test2',
    dot: function(){
      return <Icon moduleColor={'blue'} name={'Icon-correct02'} />
    },
    rightChildren: () => 'test'
  },
  {
    leftChildren:() => 'test3',
    dotStyle:{
      width: '50px',
      height: '50px',
      border: 'none'
    },
    dot: function(){
      return <Icon moduleColor={'blue'} name={'Icon-correct02'} />
    },
    rightChildren: () => {
      return <Input style={{ width: 200, height: 200 }} placeholder="Disabled Input" disabled />
    }
  },
  {
    dot:() => '测试',
    dotStyle:{
      width: '50px',
      height: '50px',
      backgroundColor: '#ccc',
      color: '#fff'
    },
    dotBottom:() => '测试',
    leftChildren:() => 'test',
    middleStyle:{ width: '40px', minHeight: '160px' },
    rightChildren: () => {
      return <Input style={{ width: 200 }} placeholder="Disabled Input" disabled />
    }
  },
  {
    leftChildren:() => 'test',
    rightChildren: () => {
      return <Input style={{ width: 200 }} placeholder="Disabled Input" disabled />
    }

  }
]

export default class TimelineDemo extends Component {
  state = {
    pageSize: 10,
    value: 1,
    datas,
    loading: false
  }

  onPageChange = (value: number, pageSize: number) => {
    console.log(value, pageSize)

    this.setState({ loading: true })

    const { datas } = this.state
    setTimeout(() => {
      if (datas.length >= 32) return false;

      this.setState({
        datas: value * pageSize > 30 ? [...datas, ...datas.slice(0, 2)] : [...datas, ...datas.slice(0, pageSize)],
        value,
        pageSize,
        loading: false
      })
    }, 200)
  }

  getPageInfo = () => {
    const { pageSize, value } = this.state
    return {
      total: 32,
      paginationType: 'scroll',
      pageSize,
      value,
      onChange: this.onPageChange,
      onSizeChange: this.onPageChange,
    } as PaginationProps & { paginationType?: PaginationType }
  }

  render() {
    const { datas, loading } = this.state

    return (
      <Timeline
                middleStyle={{width:'40px', minHeight: '80px'}}
                datas={datas}
                height={'400px'}
                pageInfo={this.getPageInfo()}
                loading={loading}
      >
      </Timeline>
    )
  }
}
```

## API

### Timeline参数说明如下：

| 参数 | 说明 | 类型 | 是否必填 | 默认值 |
| --- | --- | --- | --- | --- |
| leftStyle | 节点左侧元素样式 | CSSProperties | 否 | {width: '40px'} |
| dotStyle | 节点元素样式 | CSSProperties | 否 | {width: '40px', height: '40px'} |
| middleStyle | 节点中部元素样式 | CSSProperties | 否 | {width: '40px', minHeight: '80px'} |
| datas | 节点数据 | Array | 否 | 无 |
| pageInfo | 分页配置信息 | PaginationProps & { paginationType?: PaginationType, hasMore?: boolean } | 否 | 无 |
| loading | 分页配置loading状态 |  | boolean | false |
| height | 列表容器高度（滚动分页配置） | string | 否 | 无 |
### Timeline.data参数说明如下：

| 参数 | 说明 | 类型 | 是否必填 | 默认值 |
| --- | --- | --- | --- | --- |
| leftChildren | 节点左侧元素 | () => ReactElement | 否 | 无 |
| rightChildren | 节点右侧元素 | () => ReactElement | 否 | 无 |
| dot | 节点元素 | () => ReactElement | 否 | 无 |
| dotBottom | 节点底部元素 | () => ReactElement | 否 | 无 |
| leftStyle | 节点左侧元素样式 | CSSProperties | 否 | {width: '40px'} |
| dotStyle | 节点元素样式 | CSSProperties | 否 | {width: '40px', height: '40px'} |
| middleStyle | 节点中部元素样式 | CSSProperties | 否 | {width: '40px', minHeight: '80px'} |
使用说明：datas 内单独配置的参数会覆盖 Timeline 的参数
