Browse Source

Timeline: add timeline component (#14248)

* Table: fix params order of row events (#12086)

* Timeline: add timeline component (#11736)

* Timeline: add timeline component

* Timeline: add timeline component

* Timeline: add test case

* Timeline: fix icon class

* Timeline: update docs

* Timeline: fix test case

* Timeline: remove pending attribute

* Timeline: fix docs

* Timeline: make reverse default to false

* Timline: fix test case

* Timeline: update element-ui.d.ts

* Timeline: optimize code
Zhi Cun 6 years ago
parent
commit
f17533ab11

+ 3 - 1
components.json

@@ -67,5 +67,7 @@
   "header": "./packages/header/index.js",
   "aside": "./packages/aside/index.js",
   "main": "./packages/main/index.js",
-  "footer": "./packages/footer/index.js"
+  "footer": "./packages/footer/index.js",
+  "timeline": "./packages/timeline/index.js",
+  "timeline-item": "./packages/timeline-item/index.js"
 }

+ 3 - 3
examples/docs/en-US/table.md

@@ -2097,9 +2097,9 @@ You can customize row index in `type=index` columns.
 | cell-mouse-leave | triggers when hovering out of a cell | row, column, cell, event |
 | cell-click | triggers when clicking a cell | row, column, cell, event |
 | cell-dblclick | triggers when double clicking a cell | row, column, cell, event |
-| row-click | triggers when clicking a row | row, event, column |
-| row-contextmenu | triggers when user right clicks on a row | row, event |
-| row-dblclick | triggers when double clicking a row | row, event |
+| row-click | triggers when clicking a row | row, column, event |
+| row-contextmenu | triggers when user right clicks on a row | row, column, event |
+| row-dblclick | triggers when double clicking a row | row, column, event |
 | header-click | triggers when clicking a column header | column, event |
 | header-contextmenu | triggers when user right clicks on a column header | column, event |
 | sort-change | triggers when Table's sorting changes | { column, prop, order } |

+ 199 - 0
examples/docs/en-US/timeline.md

@@ -0,0 +1,199 @@
+<script>
+  export default {
+    data() {
+      return {
+        reverse: true,
+        activities: [{
+          content: 'Event start',
+          timestamp: '2018-04-15'
+        }, {
+          content: 'Approved',
+          timestamp: '2018-04-13'
+        }, {
+          content: 'Success',
+          timestamp: '2018-04-11'
+        }],
+        activities2: [{
+          content: 'Custom icon',
+          timestamp: '2018-04-12 20:46',
+          size: 'large',
+          type: 'primary',
+          icon: 'el-icon-more'
+        }, {
+          content: 'Custom color',
+          timestamp: '2018-04-03 20:46',
+          color: '#0bbd87'
+        }, {
+          content: 'Custom size',
+          timestamp: '2018-04-03 20:46',
+          size: 'large'
+        }, {
+          content: 'Default node',
+          timestamp: '2018-04-03 20:46'
+        }]
+      };
+    }
+  };
+</script>
+<style>
+  .demo-timeline .source .radio {
+    margin-bottom: 20px;
+  }
+  .demo-timeline .source .radio .el-radio-group {
+    margin-left: 20px;
+  }
+</style>
+
+## Timeline
+
+Visually display timeline.
+
+### Basic usage
+
+Timeline can be split into multiple activities in ascending or descending. Timestamps are important features that distinguish them from other components. Note the difference with Steps.
+
+:::demo
+```html
+<div class="block">
+  <div class="radio">
+    Order: 
+    <el-radio-group v-model="reverse">
+      <el-radio :label="true">descending</el-radio>
+      <el-radio :label="false">ascending</el-radio>
+    </el-radio-group>
+  </div>
+
+  <el-timeline :reverse="reverse">
+    <el-timeline-item
+      v-for="(activity, index) in activities"
+      :key="index"
+      :timestamp="activity.timestamp">
+      {{activity.content}}
+    </el-timeline-item>
+  </el-timeline>
+</div>
+
+<script>
+  export default {
+    data() {
+      return {
+        reverse: true,
+        activities: [{
+          content: 'Event start',
+          timestamp: '2018-04-15'
+        }, {
+          content: 'Approved',
+          timestamp: '2018-04-13'
+        }, {
+          content: 'Success',
+          timestamp: '2018-04-11'
+        }]
+      };
+    }
+  };
+</script>
+```
+:::
+
+### Custom node
+
+Size, color, and icons can be customized in node.
+
+:::demo
+```html
+<div class="block">
+  <el-timeline>
+    <el-timeline-item
+      v-for="(activity, index) in activities2"
+      :key="index"
+      :icon="activity.icon"
+      :type="activity.type"
+      :color="activity.color"
+      :size="activity.size"
+      :timestamp="activity.timestamp">
+      {{activity.content}}
+    </el-timeline-item>
+  </el-timeline>
+</div>
+
+<script>
+  export default {
+    data() {
+      return {
+        activities2: [{
+          content: 'Custom icon',
+          timestamp: '2018-04-12 20:46',
+          size: 'large',
+          type: 'primary',
+          icon: 'el-icon-more'
+        }, {
+          content: 'Custom color',
+          timestamp: '2018-04-03 20:46',
+          color: '#0bbd87'
+        }, {
+          content: 'Custom size',
+          timestamp: '2018-04-03 20:46',
+          size: 'large'
+        }, {
+          content: 'Default node',
+          timestamp: '2018-04-03 20:46'
+        }]
+      };
+    }
+  };
+</script>
+```
+:::
+
+### Custom timestamp
+
+Timestamp can be placed on top of content when content is too high.
+
+:::demo
+```html
+<div class="block">
+  <el-timeline>
+    <el-timeline-item timestamp="2018/4/12" placement="top">
+      <el-card>
+        <h4>Update Github template</h4>
+        <p>Tom committed 2018/4/12 20:46</p>
+      </el-card>
+    </el-timeline-item>
+    <el-timeline-item timestamp="2018/4/3" placement="top">
+      <el-card>
+        <h4>Update Github template</h4>
+        <p>Tom committed 2018/4/3 20:46</p>
+      </el-card>
+    </el-timeline-item>
+    <el-timeline-item timestamp="2018/4/2" placement="top">
+      <el-card>
+        <h4>Update Github template</h4>
+        <p>Tom committed 2018/4/2 20:46</p>
+      </el-card>
+    </el-timeline-item>
+  </el-timeline>
+</div>
+```
+:::
+
+### Timeline Attributes
+| Attribute      | Description    | Type      | Accepted Values | Default   |
+|---------- |-------- |---------- |-------------  |-------- |
+| reverse | whether the node is ascending or descending, default is ascending | boolean | — | false |
+
+### Timeline-item Attributes
+| Attribute      | Description    | Type      | Accepted Values | Default   |
+|---------- |-------- |---------- |-------------  |-------- |
+| timestamp     | timestamp content | string  | - | — |
+| hide-timestamp  | whether to show timestamp | boolean | — | false |
+| placement | position of timestamp | string | top / bottom | bottom |
+| type | node type | string | primary / success / warning / danger / info | - |
+| color | background color of node | string | hsl / hsv / hex / rgb | - |
+| size | node size | string | normal / large | normal |
+| icon | icon class name | string | — | - |
+
+### Timeline-Item Slot
+| name | Description |
+|------|--------|
+| — | Custom content for timeline item |
+| dot | Custom defined node |

+ 3 - 3
examples/docs/es/table.md

@@ -2099,9 +2099,9 @@ Puede personalizar el índice de la fila con la propiedad `type=index` de las co
 | cell-mouse-leave   | se dispara cuando se desplaza fuera de una celda | row, column, cell, event          |
 | cell-click         | se dispara cuando se hace clic en una celda | row, column, cell, event          |
 | cell-dblclick      | se dispara cuando se hace doble clic en una celda | row, column, cell, event          |
-| row-click          | se dispara cuando se hace clic en una fila | row, event, column                |
-| row-contextmenu    | se dispara cuando el usuario hace clic derecho en una fila | row, event                        |
-| row-dblclick       | se dispara cuando se hace doble clic en una fila | row, event                        |
+| row-click          | se dispara cuando se hace clic en una fila | row, column, event                |
+| row-contextmenu    | se dispara cuando el usuario hace clic derecho en una fila | row, column, event                        |
+| row-dblclick       | se dispara cuando se hace doble clic en una fila | row, column, event                        |
 | header-click       | se dispara cuando se hace click en una cabecera de columna | column, event                     |
 | header-contextmenu | se dispara cuando el usuario hace clic derecho en una cabecera de columna | column, event                     |
 | sort-change        | se dispara cuando el ordenamiento de la tabla cambia | { column, prop, order }           |

+ 199 - 0
examples/docs/es/timeline.md

@@ -0,0 +1,199 @@
+<script>
+  export default {
+    data() {
+      return {
+        reverse: true,
+        activities: [{
+          content: 'Success',
+          timestamp: '2018-04-11'
+        }, {
+          content: 'Approved',
+          timestamp: '2018-04-13'
+        }, {
+          content: 'Event start',
+          timestamp: '2018-04-15'
+        }],
+        activities2: [{
+          content: 'Custom icon',
+          timestamp: '2018-04-12 20:46',
+          size: 'large',
+          type: 'primary',
+          icon: 'el-icon-more'
+        }, {
+          content: 'Custom color',
+          timestamp: '2018-04-03 20:46',
+          color: '#0bbd87'
+        }, {
+          content: 'Custom size',
+          timestamp: '2018-04-03 20:46',
+          size: 'large'
+        }, {
+          content: 'Default node',
+          timestamp: '2018-04-03 20:46'
+        }]
+      };
+    }
+  };
+</script>
+<style>
+  .demo-timeline .source .radio {
+    margin-bottom: 20px;
+  }
+  .demo-timeline .source .radio .el-radio-group {
+    margin-left: 20px;
+  }
+</style>
+
+## Timeline
+
+Visually display timeline.
+
+### Basic usage
+
+Timeline can be split into multiple activities in ascending or descending. Timestamps are important features that distinguish them from other components. Note the difference with Steps.
+
+:::demo
+```html
+<div class="block">
+  <div class="radio">
+    Order: 
+    <el-radio-group v-model="reverse">
+      <el-radio :label="true">descending</el-radio>
+      <el-radio :label="false">ascending</el-radio>
+    </el-radio-group>
+  </div>
+
+  <el-timeline :reverse="reverse">
+    <el-timeline-item
+      v-for="(activity, index) in activities"
+      :key="index"
+      :timestamp="activity.timestamp">
+      {{activity.content}}
+    </el-timeline-item>
+  </el-timeline>
+</div>
+
+<script>
+  export default {
+    data() {
+      return {
+        reverse: true,
+        activities: [{
+          content: 'Success',
+          timestamp: '2018-04-11'
+        }, {
+          content: 'Approved',
+          timestamp: '2018-04-13'
+        }, {
+          content: 'Event start',
+          timestamp: '2018-04-15'
+        }]
+      };
+    }
+  };
+</script>
+```
+:::
+
+### Custom node
+
+Size, color, and icons can be customized in node.
+
+:::demo
+```html
+<div class="block">
+  <el-timeline>
+    <el-timeline-item
+      v-for="(activity, index) in activities2"
+      :key="index"
+      :icon="activity.icon"
+      :type="activity.type"
+      :color="activity.color"
+      :size="activity.size"
+      :timestamp="activity.timestamp">
+      {{activity.content}}
+    </el-timeline-item>
+  </el-timeline>
+</div>
+
+<script>
+  export default {
+    data() {
+      return {
+        activities2: [{
+          content: 'Custom icon',
+          timestamp: '2018-04-12 20:46',
+          size: 'large',
+          type: 'primary',
+          icon: 'el-icon-more'
+        }, {
+          content: 'Custom color',
+          timestamp: '2018-04-03 20:46',
+          color: '#0bbd87'
+        }, {
+          content: 'Custom size',
+          timestamp: '2018-04-03 20:46',
+          size: 'large'
+        }, {
+          content: 'Default node',
+          timestamp: '2018-04-03 20:46'
+        }]
+      };
+    }
+  };
+</script>
+```
+:::
+
+### Custom timestamp
+
+Timestamp can be placed on top of content when content is too high.
+
+:::demo
+```html
+<div class="block">
+  <el-timeline>
+    <el-timeline-item timestamp="2018/4/12" placement="top">
+      <el-card>
+        <h4>Update Github template</h4>
+        <p>Tom committed 2018/4/12 20:46</p>
+      </el-card>
+    </el-timeline-item>
+    <el-timeline-item timestamp="2018/4/3" placement="top">
+      <el-card>
+        <h4>Update Github template</h4>
+        <p>Tom committed 2018/4/3 20:46</p>
+      </el-card>
+    </el-timeline-item>
+    <el-timeline-item timestamp="2018/4/2" placement="top">
+      <el-card>
+        <h4>Update Github template</h4>
+        <p>Tom committed 2018/4/2 20:46</p>
+      </el-card>
+    </el-timeline-item>
+  </el-timeline>
+</div>
+```
+:::
+
+### Timeline Attributes
+| Attribute      | Description    | Type      | Accepted Values | Default   |
+|---------- |-------- |---------- |-------------  |-------- |
+| reverse | whether the node is ascending or descending, default is ascending | boolean | — | false |
+
+### Timeline-item Attributes
+| Attribute      | Description    | Type      | Accepted Values | Default   |
+|---------- |-------- |---------- |-------------  |-------- |
+| timestamp     | timestamp content | string  | - | — |
+| hide-timestamp  | whether to show timestamp | boolean | — | false |
+| placement | position of timestamp | string | top / bottom | bottom |
+| type | node type | string | primary / success / warning / danger / info | - |
+| color | background color of node | string | hsl / hsv / hex / rgb | - |
+| size | node size | string | normal / large | normal |
+| icon | icon class name | string | — | - |
+
+### Timeline-Item Slot
+| name | Description |
+|------|--------|
+| — | Custom content for timeline item |
+| dot | Custom defined node |

+ 3 - 3
examples/docs/zh-CN/table.md

@@ -2157,9 +2157,9 @@
 | cell-mouse-leave | 当单元格 hover 退出时会触发该事件 | row, column, cell, event |
 | cell-click | 当某个单元格被点击时会触发该事件 | row, column, cell, event |
 | cell-dblclick | 当某个单元格被双击击时会触发该事件 | row, column, cell, event |
-| row-click | 当某一行被点击时会触发该事件 | row, event, column |
-| row-contextmenu | 当某一行被鼠标右键点击时会触发该事件 | row, event |
-| row-dblclick | 当某一行被双击时会触发该事件 | row, event |
+| row-click | 当某一行被点击时会触发该事件 | row, column, event |
+| row-contextmenu | 当某一行被鼠标右键点击时会触发该事件 | row, column, event |
+| row-dblclick | 当某一行被双击时会触发该事件 | row, column, event |
 | header-click | 当某一列的表头被点击时会触发该事件 | column, event |
 | header-contextmenu | 当某一列的表头被鼠标右键点击时触发该事件 | column, event |
 | sort-change | 当表格的排序条件发生变化的时候会触发该事件 | { column, prop, order } |

+ 199 - 0
examples/docs/zh-CN/timeline.md

@@ -0,0 +1,199 @@
+<script>
+  export default {
+    data() {
+      return {
+        reverse: true,
+        activities: [{
+          content: '活动按期开始',
+          timestamp: '2018-04-15'
+        }, {
+          content: '通过审核',
+          timestamp: '2018-04-13'
+        }, {
+          content: '创建成功',
+          timestamp: '2018-04-11'
+        }],
+        activities2: [{
+          content: '支持使用图标',
+          timestamp: '2018-04-12 20:46',
+          size: 'large',
+          type: 'primary',
+          icon: 'el-icon-more'
+        }, {
+          content: '支持自定义颜色',
+          timestamp: '2018-04-03 20:46',
+          color: '#0bbd87'
+        }, {
+          content: '支持自定义尺寸',
+          timestamp: '2018-04-03 20:46',
+          size: 'large'
+        }, {
+          content: '默认样式的节点',
+          timestamp: '2018-04-03 20:46'
+        }]
+      };
+    }
+  };
+</script>
+<style>
+  .demo-timeline .source .radio {
+    margin-bottom: 20px;
+  }
+  .demo-timeline .source .radio .el-radio-group {
+    margin-left: 20px;
+  }
+</style>
+
+## Timeline 时间线
+
+可视化地呈现时间流信息。
+
+### 基础用法
+
+Timeline 可拆分成多个按照时间戳正序或倒序排列的 activity,时间戳是其区分于其他控件的重要特征,使⽤时注意与 Steps 步骤条等区分。
+
+:::demo
+```html
+<div class="block">
+  <div class="radio">
+    排序:
+    <el-radio-group v-model="reverse">
+      <el-radio :label="true">倒序</el-radio>
+      <el-radio :label="false">正序</el-radio>
+    </el-radio-group>
+  </div>
+
+  <el-timeline :reverse="reverse">
+    <el-timeline-item
+      v-for="(activity, index) in activities"
+      :key="index"
+      :timestamp="activity.timestamp">
+      {{activity.content}}
+    </el-timeline-item>
+  </el-timeline>
+</div>
+
+<script>
+  export default {
+    data() {
+      return {
+        reverse: true,
+        activities: [{
+          content: '活动按期开始',
+          timestamp: '2018-04-15'
+        }, {
+          content: '通过审核',
+          timestamp: '2018-04-13'
+        }, {
+          content: '创建成功',
+          timestamp: '2018-04-11'
+        }]
+      };
+    }
+  };
+</script>
+```
+:::
+
+### ⾃定义节点样式
+
+可根据实际场景⾃定义节点尺⼨、颜⾊,或直接使⽤图标。
+
+:::demo
+```html
+<div class="block">
+  <el-timeline>
+    <el-timeline-item
+      v-for="(activity, index) in activities2"
+      :key="index"
+      :icon="activity.icon"
+      :type="activity.type"
+      :color="activity.color"
+      :size="activity.size"
+      :timestamp="activity.timestamp">
+      {{activity.content}}
+    </el-timeline-item>
+  </el-timeline>
+</div>
+
+<script>
+  export default {
+    data() {
+      return {
+        activities2: [{
+          content: '支持使用图标',
+          timestamp: '2018-04-12 20:46',
+          size: 'large',
+          type: 'primary',
+          icon: 'el-icon-more'
+        }, {
+          content: '支持自定义颜色',
+          timestamp: '2018-04-03 20:46',
+          color: '#0bbd87'
+        }, {
+          content: '支持自定义尺寸',
+          timestamp: '2018-04-03 20:46',
+          size: 'large'
+        }, {
+          content: '默认样式的节点',
+          timestamp: '2018-04-03 20:46'
+        }]
+      };
+    }
+  };
+</script>
+```
+:::
+
+### ⾃定义时间戳
+
+当内容在垂直⽅向上过⾼时,可将时间戳置于内容之上。
+
+:::demo
+```html
+<div class="block">
+  <el-timeline>
+    <el-timeline-item timestamp="2018/4/12" placement="top">
+      <el-card>
+        <h4>更新 Github 模板</h4>
+        <p>王小虎 提交于 2018/4/12 20:46</p>
+      </el-card>
+    </el-timeline-item>
+    <el-timeline-item timestamp="2018/4/3" placement="top">
+      <el-card>
+        <h4>更新 Github 模板</h4>
+        <p>王小虎 提交于 2018/4/3 20:46</p>
+      </el-card>
+    </el-timeline-item>
+    <el-timeline-item timestamp="2018/4/2" placement="top">
+      <el-card>
+        <h4>更新 Github 模板</h4>
+        <p>王小虎 提交于 2018/4/2 20:46</p>
+      </el-card>
+    </el-timeline-item>
+  </el-timeline>
+</div>
+```
+:::
+
+### Timeline Attributes
+| 参数      | 说明    | 类型      | 可选值       | 默认值   |
+|---------- |-------- |---------- |-------------  |-------- |
+| reverse | 指定节点排序方向,默认为正序 | boolean | — | false |
+
+### Timeline-item Attributes
+| 参数      | 说明    | 类型      | 可选值       | 默认值   |
+|---------- |-------- |---------- |-------------  |-------- |
+| timestamp     | 时间戳 | string  | - | — |
+| hide-timestamp  | 是否隐藏时间戳 | boolean | — | false |
+| placement | 时间戳位置 | string | top / bottom | bottom |
+| type | 节点类型 | string | primary / success / warning / danger / info | - |
+| color | 节点颜色 | string | hsl / hsv / hex / rgb | - |
+| size | 节点尺寸 | string | normal / large | normal |
+| icon | 节点图标 | string | — | - |
+
+### Timeline-Item Slot
+| name | 说明 |
+|------|--------|
+| — | Timeline-Item 的内容 |
+| dot | 自定义节点 |

+ 12 - 0
examples/nav.config.json

@@ -243,6 +243,10 @@
             {
               "path": "/collapse",
               "title": "Collapse 折叠面板"
+            },
+            {
+              "path": "/timeline",
+              "title": "Timeline 时间线"
             }
           ]
         }
@@ -493,6 +497,10 @@
             {
               "path": "/collapse",
               "title": "Collapse"
+            },
+            {
+              "path": "/timeline",
+              "title": "Timeline"
             }
           ]
         }
@@ -743,6 +751,10 @@
             {
               "path": "/collapse",
               "title": "Collapse"
+            },
+            {
+              "path": "/timeline",
+              "title": "Timeline"
             }
           ]
         }

+ 1 - 1
packages/table/src/table-body.js

@@ -381,7 +381,7 @@ export default {
           table.$emit(`cell-${name}`, row, column, cell, event);
         }
       }
-      table.$emit(`row-${name}`, row, event, column);
+      table.$emit(`row-${name}`, row, column, event);
     },
 
     handleExpandClick(row, e) {

+ 6 - 0
packages/theme-chalk/src/common/var.scss

@@ -683,6 +683,12 @@ $--footer-padding: 0 20px !default;
 --------------------------*/
 $--main-padding: 20px !default;
 
+/* Timeline
+--------------------------*/
+$--timeline-node-size-normal: 12px !default;
+$--timeline-node-size-large: 14px !default;
+$--timeline-node-color: $--border-color-light !default;
+
 /* Break-point
 --------------------------*/
 $--sm: 768px !default;

+ 2 - 0
packages/theme-chalk/src/index.scss

@@ -65,3 +65,5 @@
 @import "./aside.scss";
 @import "./main.scss";
 @import "./footer.scss";
+@import "./timeline.scss";
+@import "./timeline-item.scss";

+ 86 - 0
packages/theme-chalk/src/timeline-item.scss

@@ -0,0 +1,86 @@
+@import "mixins/mixins";
+@import "common/var";
+
+@include b(timeline-item) {
+  position: relative;
+  padding-bottom: 20px;
+
+  @include e(wrapper) {
+    position: relative;
+    padding-left: 28px;
+    top: -3px;
+  }
+
+  @include e(tail) {
+    position: absolute;
+    left: 4px;
+    height: 100%;
+    border-left: 2px solid $--timeline-node-color;
+  }
+
+  @include e(icon) {
+    color: $--color-white;
+    font-size: $--font-size-small;
+  }
+
+  @include e(node) {
+    position: absolute;
+    background-color: $--timeline-node-color;
+    border-radius: 50%;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+
+    @include m(normal) {
+      left: -1px;
+      width: $--timeline-node-size-normal;
+      height: $--timeline-node-size-normal;
+    }
+    @include m(large) {
+      left: -2px;
+      width: $--timeline-node-size-large;
+      height: $--timeline-node-size-large;
+    }
+
+    @include m(primary) {
+      background-color: $--color-primary;
+    }
+    @include m(success) {
+      background-color: $--color-success;
+    }
+    @include m(warning) {
+      background-color: $--color-warning;
+    }
+    @include m(danger) {
+      background-color: $--color-danger;
+    }
+    @include m(info) {
+      background-color: $--color-info;
+    }
+  }
+
+  @include e(dot) {
+    position: absolute;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+  }
+
+  @include e(content) {
+    color: $--color-text-primary;
+  }
+
+  @include e(timestamp) {
+    color: $--color-text-secondary;
+    line-height: 1;
+    font-size: $--font-size-small;
+
+    @include when(top) {
+      margin-bottom: 8px;
+      padding-top: 4px;
+    }
+    @include when(bottom) {
+      margin-top: 8px;
+    }
+  }
+}

+ 14 - 0
packages/theme-chalk/src/timeline.scss

@@ -0,0 +1,14 @@
+@import "mixins/mixins";
+@import "common/var";
+
+@include b(timeline) {
+  margin: 0;
+  font-size: $--font-size-base;
+  list-style: none;
+
+  & .el-timeline-item:last-child {
+    & .el-timeline-item__tail {
+      display: none;
+    }
+  }
+}

+ 8 - 0
packages/timeline-item/index.js

@@ -0,0 +1,8 @@
+import ElTimelineItem from '../timeline/src/item';
+
+/* istanbul ignore next */
+ElTimelineItem.install = function(Vue) {
+  Vue.component(ElTimelineItem.name, ElTimelineItem);
+};
+
+export default ElTimelineItem;

+ 8 - 0
packages/timeline/index.js

@@ -0,0 +1,8 @@
+import Timeline from './src/main';
+
+/* istanbul ignore next */
+Timeline.install = function(Vue) {
+  Vue.component(Timeline.name, Timeline);
+};
+
+export default Timeline;

+ 73 - 0
packages/timeline/src/item.vue

@@ -0,0 +1,73 @@
+<template>
+  <li class="el-timeline-item">
+    <div class="el-timeline-item__tail"></div>
+
+    <div v-if="!$slots.dot"
+      class="el-timeline-item__node"
+      :class="[
+        `el-timeline-item__node--${size || ''}`,
+        `el-timeline-item__node--${type || ''}`
+      ]"
+      :style="{
+        backgroundColor: color
+      }"
+    >
+      <i v-if="icon"
+        class="el-timeline-item__icon"
+        :class="icon"
+      ></i>
+    </div>
+    <div v-if="$slots.dot" class="el-timeline-item__dot">
+      <slot name="dot"></slot>
+    </div>
+
+    <div class="el-timeline-item__wrapper">
+      <div v-if="!hideTimestamp && placement === 'top'"
+        class="el-timeline-item__timestamp is-top">
+        {{timestamp}}
+      </div>
+
+      <div class="el-timeline-item__content">
+        <slot></slot>
+      </div>
+
+      <div v-if="!hideTimestamp && placement === 'bottom'"
+        class="el-timeline-item__timestamp is-bottom">
+        {{timestamp}}
+      </div>
+    </div>
+  </li>
+</template>
+
+<script>
+  export default {
+    name: 'ElTimelineItem',
+
+    inject: ['timeline'],
+
+    props: {
+      timestamp: String,
+
+      hideTimestamp: {
+        type: Boolean,
+        default: false
+      },
+
+      placement: {
+        type: String,
+        default: 'bottom'
+      },
+
+      type: String,
+
+      color: String,
+
+      size: {
+        type: String,
+        default: 'normal'
+      },
+
+      icon: String
+    }
+  };
+</script>

+ 38 - 0
packages/timeline/src/main.vue

@@ -0,0 +1,38 @@
+<template>
+  <ul class="el-timeline"
+    :class="{
+      'is-reverse': reverse
+    }">
+    <slot></slot>
+  </ul>
+</template>
+
+<script>
+  export default {
+    name: 'ElTimeline',
+
+    props: {
+      reverse: {
+        type: Boolean,
+        default: false
+      }
+    },
+
+    provide() {
+      return {
+        timeline: this
+      };
+    },
+
+    watch: {
+      reverse: {
+        handler(newVal) {
+          if (newVal) {
+            this.$slots.default = [...this.$slots.default].reverse();
+          }
+        },
+        immediate: true
+      }
+    }
+  };
+</script>

+ 7 - 1
src/index.js

@@ -69,6 +69,8 @@ import Header from '../packages/header/index.js';
 import Aside from '../packages/aside/index.js';
 import Main from '../packages/main/index.js';
 import Footer from '../packages/footer/index.js';
+import Timeline from '../packages/timeline/index.js';
+import TimelineItem from '../packages/timeline-item/index.js';
 import locale from 'element-ui/src/locale';
 import CollapseTransition from 'element-ui/src/transitions/collapse-transition';
 
@@ -138,6 +140,8 @@ const components = [
   Aside,
   Main,
   Footer,
+  Timeline,
+  TimelineItem,
   CollapseTransition
 ];
 
@@ -245,5 +249,7 @@ export default {
   Header,
   Aside,
   Main,
-  Footer
+  Footer,
+  Timeline,
+  TimelineItem
 };

+ 228 - 0
test/unit/specs/timeline.spec.js

@@ -0,0 +1,228 @@
+import { createVue, destroyVM } from '../util';
+
+describe('Timeline', () => {
+  let vm;
+  afterEach(() => {
+    destroyVM(vm);
+  });
+
+  it('create', () => {
+    vm = createVue({
+      template: `
+        <el-timeline>
+          <el-timeline-item
+            v-for="(activity, index) in activities"
+            :key="index"
+            :timestamp="activity.timestamp">
+            {{activity.content}}
+          </el-timeline-item>
+        </el-timeline>
+      `,
+      data() {
+        return {
+          activities: [{
+            content: '创建成功',
+            timestamp: '2018-04-11'
+          }, {
+            content: '通过审核',
+            timestamp: '2018-04-13'
+          }, {
+            content: '活动按期开始',
+            timestamp: '2018-04-15'
+          }]
+        };
+      }
+    }, true);
+    let contentElms = vm.$el.querySelectorAll('.el-timeline-item__content');
+    contentElms.forEach((elm, index) => {
+      expect(elm.innerText).to.equal(vm.activities[index].content);
+    });
+    let timestampElms = vm.$el.querySelectorAll('.el-timeline-item__timestamp');
+    timestampElms.forEach((elm, index) => {
+      expect(elm.innerText).to.equal(vm.activities[index].timestamp);
+    });
+  });
+
+  it('reverse', done => {
+    vm = createVue({
+      template: `
+        <el-timeline :reverse="reverse">
+          <el-timeline-item
+            v-for="(activity, index) in activities"
+            :key="index"
+            :timestamp="activity.timestamp">
+            {{activity.content}}
+          </el-timeline-item>
+        </el-timeline>
+      `,
+
+      data() {
+        return {
+          reverse: true,
+          activities: [{
+            content: '创建成功',
+            timestamp: '2018-04-11'
+          }, {
+            content: '通过审核',
+            timestamp: '2018-04-13'
+          }, {
+            content: '活动按期开始',
+            timestamp: '2018-04-15'
+          }]
+        };
+      }
+    }, true);
+
+    const contentElms = vm.$el.querySelectorAll('.el-timeline-item__content');
+    contentElms.forEach((elm, index) => {
+      expect(elm.innerText).to.equal(vm.activities[vm.activities.length - index - 1].content);
+    });
+
+    vm.reverse = false;
+    vm.$nextTick(() => {
+      const contentElms = vm.$el.querySelectorAll('.el-timeline-item__content');
+      contentElms.forEach((elm, index) => {
+        expect(elm.innerText).to.equal(vm.activities[index].content);
+      });
+      done();
+    });
+  });
+
+  it('placement', () => {
+    vm = createVue({
+      template: `
+        <el-timeline>
+          <el-timeline-item
+            v-for="(activity, index) in activities"
+            :key="index"
+            :timestamp="activity.timestamp"
+            :placement="activity.placement">
+            {{activity.content}}
+          </el-timeline-item>
+        </el-timeline>
+      `,
+
+      data() {
+        return {
+          activities: [{
+            content: '创建成功',
+            timestamp: '2018-04-11',
+            placement: 'top'
+          }, {
+            content: '通过审核',
+            timestamp: '2018-04-13'
+          }, {
+            content: '活动按期开始',
+            timestamp: '2018-04-15'
+          }]
+        };
+      }
+    }, true);
+
+    const timestampElm = vm.$el.querySelectorAll('.el-timeline-item__timestamp')[0];
+    expect(timestampElm.classList.contains('is-top')).to.true;
+  });
+
+  it('hide-timestamp', () => {
+    vm = createVue({
+      template: `
+        <el-timeline>
+          <el-timeline-item
+            v-for="(activity, index) in activities"
+            :key="index"
+            :timestamp="activity.timestamp"
+            :hide-timestamp="activity.hideTimestamp">
+            {{activity.content}}
+          </el-timeline-item>
+        </el-timeline>
+      `,
+
+      data() {
+        return {
+          activities: [{
+            content: '创建成功',
+            timestamp: '2018-04-11',
+            hideTimestamp: true
+          }, {
+            content: '通过审核',
+            timestamp: '2018-04-13'
+          }, {
+            content: '活动按期开始',
+            timestamp: '2018-04-15'
+          }]
+        };
+      }
+    }, true);
+
+    const timestampElms = vm.$el.querySelectorAll('.el-timeline-item__timestamp');
+    expect(timestampElms.length).to.equal(2);
+  });
+
+  it('color', () => {
+    vm = createVue({
+      template: `
+        <el-timeline>
+          <el-timeline-item
+            timestamp="2018-04-11"
+            color="#f00">
+            创建成功
+          </el-timeline-item>
+        </el-timeline>
+      `
+    }, true);
+
+    const nodeElm = vm.$el.querySelector('.el-timeline-item__node');
+    expect(nodeElm.style.backgroundColor).to.equal('rgb(255, 0, 0)');
+  });
+
+  it('type', () => {
+    vm = createVue({
+      template: `
+        <el-timeline>
+          <el-timeline-item
+            timestamp="2018-04-11"
+            type="primary">
+            创建成功
+          </el-timeline-item>
+        </el-timeline>
+      `
+    }, true);
+
+    const nodeElm = vm.$el.querySelector('.el-timeline-item__node');
+    expect(nodeElm.classList.contains('el-timeline-item__node--primary')).to.true;
+  });
+
+  it('size', () => {
+    vm = createVue({
+      template: `
+        <el-timeline>
+          <el-timeline-item
+            timestamp="2018-04-11"
+            type="large">
+            创建成功
+          </el-timeline-item>
+        </el-timeline>
+      `
+    }, true);
+
+    const nodeElm = vm.$el.querySelector('.el-timeline-item__node');
+    expect(nodeElm.classList.contains('el-timeline-item__node--large')).to.true;
+  });
+
+  it('icon', () => {
+    vm = createVue({
+      template: `
+        <el-timeline>
+          <el-timeline-item
+            timestamp="2018-04-11"
+            icon="el-icon-more">
+            创建成功
+          </el-timeline-item>
+        </el-timeline>
+      `
+    }, true);
+
+    const nodeElm = vm.$el.querySelector('.el-timeline-item__icon');
+    expect(nodeElm.classList.contains('el-icon-more')).to.true;
+  });
+});

+ 8 - 0
types/element-ui.d.ts

@@ -61,6 +61,8 @@ import { ElTableColumn } from './table-column'
 import { ElTag } from './tag'
 import { ElTabs } from './tabs'
 import { ElTabPane } from './tab-pane'
+import { ElTimeline } from './timeline'
+import { ElTimelineItem } from './timeline-item'
 import { ElTimePicker } from './time-picker'
 import { ElTimeSelect } from './time-select'
 import { ElTooltip } from './tooltip'
@@ -275,6 +277,12 @@ export class TabPane extends ElTabPane {}
 /** Tag Component */
 export class Tag extends ElTag {}
 
+/** Timeline Component */
+export class Timeline extends ElTimeline {}
+
+/** Timeline Item Component */
+export class TimelineItem extends ElTimelineItem {}
+
 /** TimePicker Component */
 export class TimePicker extends ElTimePicker {}
 

+ 20 - 0
types/timeline-item.d.ts

@@ -0,0 +1,20 @@
+import { ElementUIComponent } from './component'
+
+export type TimelineItemPlacement = 'top' | 'bottom'
+export type TimelineItemType = 'primary' | 'success' | 'warning' | 'danger' | 'info'
+export type TimelineItemSize = 'normal' | 'large'
+
+/** TimelineItem Component */
+export declare class ElTimelineItem extends ElementUIComponent {
+  timestamp: string
+
+  hideTimestamp: boolean
+
+  placement: TimelineItemPlacement
+
+  type: TimelineItemType
+
+  size: TimelineItemSize
+
+  icon: string
+}

+ 6 - 0
types/timeline.d.ts

@@ -0,0 +1,6 @@
+import { ElementUIComponent } from './component'
+
+/** Timeline Component */
+export declare class ElTimeline extends ElementUIComponent {
+  reverse: boolean
+}