浏览代码

Avatar: Add avatar component (#16144)

* add avatar component

* 1. add test 2. add types 3. refine doc 4. add img attr

* add props in types and doc

* refine how image fit its container

* fix doc

* refine doc

* change default background color

* remote style demo from doc

* add theme

* add demo on theme preview and change var name
luckyCao 6 年之前
父节点
当前提交
6f9ce3accb

+ 2 - 1
components.json

@@ -77,5 +77,6 @@
   "backtop": "./packages/backtop/index.js",
   "infinite-scroll": "./packages/infinite-scroll/index.js",
   "page-header": "./packages/page-header/index.js",
-  "cascader-panel": "./packages/cascader-panel/index.js"
+  "cascader-panel": "./packages/cascader-panel/index.js",
+  "avatar": "./packages/avatar/index.js"
 }

+ 14 - 1
examples/components/theme/components-preview.vue

@@ -25,6 +25,10 @@
   .el-carousel__item:nth-child(2n + 1) {
     background-color: #d3dce6;
   }
+
+  .el-avatar:not(:last-child) {
+    margin-right: 20px;
+  }
 }
 </style>
 <template>
@@ -140,7 +144,7 @@
     </el-row>
     <h4>Rate</h4>
     <el-row>
-      <el-rate class="demo-line" v-model="rate"></el-rate> 
+      <el-rate class="demo-line" v-model="rate"></el-rate>
       <el-rate
         class="demo-line"
         v-model="rate"
@@ -354,6 +358,12 @@
         </el-collapse-item>
       </el-collapse>
     </el-row>
+    <h4>Avatar</h4>
+    <el-row>
+      <el-avatar icon="el-icon-user-solid"/>
+      <el-avatar> avatar </el-avatar>
+      <el-avatar shape="square" :size="60" fit="contain" :src="avatarData.url"></el-avatar>
+    </el-row>
   </div>
 </template>
 <script>
@@ -511,6 +521,9 @@ export default {
       defaultTreeProps: {
         children: 'children',
         label: 'label'
+      },
+      avatarData: {
+        url: 'https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg'
       }
     };
   }

+ 62 - 0
examples/demo-styles/avatar.scss

@@ -0,0 +1,62 @@
+.demo-avatar {
+
+  &.demo-basic {
+    text-align: center;
+
+    .demo-basic--circle, .demo-basic--square {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      .block {
+        flex: 1;
+      }
+
+      .block:not(:last-child) {
+        border-right: 1px solid rgba(224, 230, 237, 0.5);
+      }
+    }
+
+  }
+
+  .sub-title {
+    margin-bottom: 10px;
+    font-size: 14px;
+    color: #8492a6;
+  }
+
+  .el-col:not(:last-child) {
+    border-right: 1px solid rgba(224,230,237,.5);
+  }
+
+  .demo-type {
+    display: flex;
+
+    >div {
+      flex: 1;
+      text-align: center;
+    }
+
+    >div:not(:last-child) {
+      border-right: 1px solid rgba(224,230,237,.5);
+    }
+  }
+
+  .demo-fit {
+    display: flex;
+    text-align: center;
+    justify-content: space-between;
+
+    .block {
+      flex: 1;
+      display: flex;
+      flex-direction: column;
+      flex-grow: 0;
+    }
+
+    .title {
+      margin-bottom: 10px;
+      font-size: 14px;
+      color: #8492a6;
+    }
+  }
+}

+ 2 - 0
examples/demo-styles/index.scss

@@ -42,3 +42,5 @@
 @import "./divider.scss";
 @import "./image.scss";
 @import "./infiniteScroll.scss";
+@import "./avatar.scss";
+

+ 145 - 0
examples/docs/en-US/avatar.md

@@ -0,0 +1,145 @@
+## Avatar avatar
+
+Avatars can be used to represent people or objects. It supports images, Icons, or characters.
+
+### Basic
+
+use `shape` and `size` prop to set avatar's shape and size
+
+:::demo
+```html
+<template>
+  <el-row class="demo-avatar demo-basic">
+    <el-col :span="12">
+      <div class="sub-title">circle</div>
+      <div class="demo-basic--circle">
+        <div class="block"><el-avatar :size="50" :src="circleUrl"></el-avatar></div>
+        <div class="block" v-for="size in sizeList" :key="size">
+          <el-avatar :size="size" :src="circleUrl"></el-avatar>
+        </div>
+      </div>
+    </el-col>  
+    <el-col :span="12">
+      <div class="sub-title">square</div>
+      <div class="demo-basic--circle">
+        <div class="block"><el-avatar shape="square" :size="50" :src="squareUrl"></el-avatar></div>
+        <div class="block" v-for="size in sizeList" :key="size">
+          <el-avatar shape="square" :size="size" :src="squareUrl"></el-avatar>
+        </div>
+      </div>
+    </el-col> 
+  </el-row>
+</template>
+<script>
+  export default {
+    data () {
+      return {
+        circleUrl: "https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png",
+        squareUrl: "https://cube.elemecdn.com/9/c2/f0ee8a3c7c9638a54940382568c9dpng.png",
+        sizeList: ["large", "medium", "small"]
+      }
+    }
+  }
+</script>
+
+```
+:::
+
+### Types
+
+It supports images, Icons, or characters
+
+:::demo
+```html
+<template>
+  <div class="demo-type">
+    <div>
+      <el-avatar icon="el-icon-user-solid"></el-avatar>
+    </div>
+    <div>
+      <el-avatar src="https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png"></el-avatar>
+    </div>
+    <div>
+      <el-avatar> user </el-avatar>
+    </div>
+  </div>
+</template>
+```
+:::
+
+### Fallback when image load error
+
+fallback when image load error
+
+:::demo
+```html
+<template>
+  <div class="demo-type">
+    <el-avatar :size="60" src="https://empty" @error="errorHandler">
+      <img src="https://cube.elemecdn.com/e/fd/0fc7d20532fdaf769a25683617711png.png"/>
+      </el-avatar>
+  </div>
+</template>
+<script>
+  export default {
+    methods: {
+      errorHandler() {
+        return true
+      }
+    }
+  }
+</script>
+
+```
+:::
+
+### How the image fit its container
+
+Set how the image fit its container for an image avatar, same as [object-fit](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit).
+
+:::demo
+```html
+<template>
+  <div class="demo-fit">
+    <div class="block" v-for="fit in fits" :key="fit">
+        <span class="title">{{ fit }}</span>
+        <el-avatar shape="square" :size="100" :fit="fit" :src="url"></el-avatar>
+    </div>
+  </div>
+</template>
+<script>
+  export default {
+    data() {
+      return {
+        fits: ['fill', 'contain', 'cover', 'none', 'scale-down'],
+        url: 'https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg'
+      }
+    }
+  }
+</script>
+
+```
+:::
+
+### Attributes
+
+| Attribute      | Description          | Type      | Accepted Values       | Default  |
+| ----------------- | -------------------------------- | --------------- | ------ | ------ |
+| icon              | set representation type to Icon, more info on Icon Component   | string          |        |        |
+| size              | set avatar size                     | number/string | number / large / medium / small | large  |
+| shape             | set avatar shape  | string |    circle / square     |   circle  |
+| src               | the address of the image for an image avatar | string |        |      |
+| srcSet            | A list of one or more strings separated by commas indicating a set of possible image sources for the user agent to use | string |        |      |
+| alt               | This attribute defines an alternative text description of the image | string |        |      |
+| fit               | set how the image fit its container for an image avatar | string |    fill / contain / cover / none / scale-down    |   cover   |
+
+### Events
+
+| Event Name | Description         | Parameters  |
+| ------ | ------------------ | -------- |
+| error  | handler when img load error, return false to prevent default fallback behavior |(e: Event)  |
+
+### Slot
+
+| Slot Name | Description | 
+| default  | customize avatar content |

+ 145 - 0
examples/docs/es/avatar.md

@@ -0,0 +1,145 @@
+## Avatar avatar
+
+Avatars can be used to represent people or objects. It supports images, Icons, or characters.
+
+### Basic
+
+use `shape` and `size` prop to set avatar's shape and size
+
+:::demo
+```html
+<template>
+  <el-row class="demo-avatar demo-basic">
+    <el-col :span="12">
+      <div class="sub-title">circle</div>
+      <div class="demo-basic--circle">
+        <div class="block"><el-avatar :size="50" :src="circleUrl"></el-avatar></div>
+        <div class="block" v-for="size in sizeList" :key="size">
+          <el-avatar :size="size" :src="circleUrl"></el-avatar>
+        </div>
+      </div>
+    </el-col>  
+    <el-col :span="12">
+      <div class="sub-title">square</div>
+      <div class="demo-basic--circle">
+        <div class="block"><el-avatar shape="square" :size="50" :src="squareUrl"></el-avatar></div>
+        <div class="block" v-for="size in sizeList" :key="size">
+          <el-avatar shape="square" :size="size" :src="squareUrl"></el-avatar>
+        </div>
+      </div>
+    </el-col> 
+  </el-row>
+</template>
+<script>
+  export default {
+    data () {
+      return {
+        circleUrl: "https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png",
+        squareUrl: "https://cube.elemecdn.com/9/c2/f0ee8a3c7c9638a54940382568c9dpng.png",
+        sizeList: ["large", "medium", "small"]
+      }
+    }
+  }
+</script>
+
+```
+:::
+
+### Types
+
+It supports images, Icons, or characters
+
+:::demo
+```html
+<template>
+  <div class="demo-type">
+    <div>
+      <el-avatar icon="el-icon-user-solid"></el-avatar>
+    </div>
+    <div>
+      <el-avatar src="https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png"></el-avatar>
+    </div>
+    <div>
+      <el-avatar> user </el-avatar>
+    </div>
+  </div>
+</template>
+```
+:::
+
+### Fallback when image load error
+
+fallback when image load error
+
+:::demo
+```html
+<template>
+  <div class="demo-type">
+    <el-avatar :size="60" src="https://empty" @error="errorHandler">
+      <img src="https://cube.elemecdn.com/e/fd/0fc7d20532fdaf769a25683617711png.png"/>
+      </el-avatar>
+  </div>
+</template>
+<script>
+  export default {
+    methods: {
+      errorHandler() {
+        return true
+      }
+    }
+  }
+</script>
+
+```
+:::
+
+### How the image fit its container
+
+Set how the image fit its container for an image avatar, same as [object-fit](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit).
+
+:::demo
+```html
+<template>
+  <div class="demo-fit">
+    <div class="block" v-for="fit in fits" :key="fit">
+        <span class="title">{{ fit }}</span>
+        <el-avatar shape="square" :size="100" :fit="fit" :src="url"></el-avatar>
+    </div>
+  </div>
+</template>
+<script>
+  export default {
+    data() {
+      return {
+        fits: ['fill', 'contain', 'cover', 'none', 'scale-down'],
+        url: 'https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg'
+      }
+    }
+  }
+</script>
+
+```
+:::
+
+### Attributes
+
+| Attribute      | Description          | Type      | Accepted Values       | Default  |
+| ----------------- | -------------------------------- | --------------- | ------ | ------ |
+| icon              | set representation type to Icon, more info on Icon Component   | string          |        |        |
+| size              | set avatar size                     | number/string | number / large / medium / small | large  |
+| shape             | set avatar shape  | string |    circle / square     |   circle  |
+| src               | the address of the image for an image avatar | string |        |      |
+| srcSet            | A list of one or more strings separated by commas indicating a set of possible image sources for the user agent to use | string |        |      |
+| alt               | This attribute defines an alternative text description of the image | string |        |      |
+| fit               | set how the image fit its container for an image avatar | string |    fill / contain / cover / none / scale-down    |   cover   |
+
+### Events
+
+| Event Name | Description         | Parameters  |
+| ------ | ------------------ | -------- |
+| error  | handler when img load error, return false to prevent default fallback behavior |(e: Event)  |
+
+### Slot
+
+| Slot Name | Description | 
+| default  | customize avatar content |

+ 145 - 0
examples/docs/fr-FR/avatar.md

@@ -0,0 +1,145 @@
+## Avatar avatar
+
+Avatars can be used to represent people or objects. It supports images, Icons, or characters.
+
+### Basic
+
+use `shape` and `size` prop to set avatar's shape and size
+
+:::demo
+```html
+<template>
+  <el-row class="demo-avatar demo-basic">
+    <el-col :span="12">
+      <div class="sub-title">circle</div>
+      <div class="demo-basic--circle">
+        <div class="block"><el-avatar :size="50" :src="circleUrl"></el-avatar></div>
+        <div class="block" v-for="size in sizeList" :key="size">
+          <el-avatar :size="size" :src="circleUrl"></el-avatar>
+        </div>
+      </div>
+    </el-col>  
+    <el-col :span="12">
+      <div class="sub-title">square</div>
+      <div class="demo-basic--circle">
+        <div class="block"><el-avatar shape="square" :size="50" :src="squareUrl"></el-avatar></div>
+        <div class="block" v-for="size in sizeList" :key="size">
+          <el-avatar shape="square" :size="size" :src="squareUrl"></el-avatar>
+        </div>
+      </div>
+    </el-col> 
+  </el-row>
+</template>
+<script>
+  export default {
+    data () {
+      return {
+        circleUrl: "https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png",
+        squareUrl: "https://cube.elemecdn.com/9/c2/f0ee8a3c7c9638a54940382568c9dpng.png",
+        sizeList: ["large", "medium", "small"]
+      }
+    }
+  }
+</script>
+
+```
+:::
+
+### Types
+
+It supports images, Icons, or characters
+
+:::demo
+```html
+<template>
+  <div class="demo-type">
+    <div>
+      <el-avatar icon="el-icon-user-solid"></el-avatar>
+    </div>
+    <div>
+      <el-avatar src="https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png"></el-avatar>
+    </div>
+    <div>
+      <el-avatar> user </el-avatar>
+    </div>
+  </div>
+</template>
+```
+:::
+
+### Fallback when image load error
+
+fallback when image load error
+
+:::demo
+```html
+<template>
+  <div class="demo-type">
+    <el-avatar :size="60" src="https://empty" @error="errorHandler">
+      <img src="https://cube.elemecdn.com/e/fd/0fc7d20532fdaf769a25683617711png.png"/>
+      </el-avatar>
+  </div>
+</template>
+<script>
+  export default {
+    methods: {
+      errorHandler() {
+        return true
+      }
+    }
+  }
+</script>
+
+```
+:::
+
+### How the image fit its container
+
+Set how the image fit its container for an image avatar, same as [object-fit](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit).
+
+:::demo
+```html
+<template>
+  <div class="demo-fit">
+    <div class="block" v-for="fit in fits" :key="fit">
+        <span class="title">{{ fit }}</span>
+        <el-avatar shape="square" :size="100" :fit="fit" :src="url"></el-avatar>
+    </div>
+  </div>
+</template>
+<script>
+  export default {
+    data() {
+      return {
+        fits: ['fill', 'contain', 'cover', 'none', 'scale-down'],
+        url: 'https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg'
+      }
+    }
+  }
+</script>
+
+```
+:::
+
+### Attributes
+
+| Attribute      | Description          | Type      | Accepted Values       | Default  |
+| ----------------- | -------------------------------- | --------------- | ------ | ------ |
+| icon              | set representation type to Icon, more info on Icon Component   | string          |        |        |
+| size              | set avatar size                     | number/string | number / large / medium / small | large  |
+| shape             | set avatar shape  | string |    circle / square     |   circle  |
+| src               | the address of the image for an image avatar | string |        |      |
+| srcSet            | A list of one or more strings separated by commas indicating a set of possible image sources for the user agent to use | string |        |      |
+| alt               | This attribute defines an alternative text description of the image | string |        |      |
+| fit               | set how the image fit its container for an image avatar | string |    fill / contain / cover / none / scale-down    |   cover   |
+
+### Events
+
+| Event Name | Description         | Parameters  |
+| ------ | ------------------ | -------- |
+| error  | handler when img load error, return false to prevent default fallback behavior |(e: Event)  |
+
+### Slot
+
+| Slot Name | Description | 
+| default  | customize avatar content |

+ 147 - 0
examples/docs/zh-CN/avatar.md

@@ -0,0 +1,147 @@
+## Avatar 头像
+
+用图标、图片或者字符的形式展示用户或事物信息。
+
+### 基本用法
+
+通过 `shape` 和 `size` 设置头像的形状和大小。
+
+:::demo
+```html
+<template>
+  <el-row class="demo-avatar demo-basic">
+    <el-col :span="12">
+      <div class="sub-title">circle</div>
+      <div class="demo-basic--circle">
+        <div class="block"><el-avatar :size="50" :src="circleUrl"></el-avatar></div>
+        <div class="block" v-for="size in sizeList" :key="size">
+          <el-avatar :size="size" :src="circleUrl"></el-avatar>
+        </div>
+      </div>
+    </el-col>  
+    <el-col :span="12">
+      <div class="sub-title">square</div>
+      <div class="demo-basic--circle">
+        <div class="block"><el-avatar shape="square" :size="50" :src="squareUrl"></el-avatar></div>
+        <div class="block" v-for="size in sizeList" :key="size">
+          <el-avatar shape="square" :size="size" :src="squareUrl"></el-avatar>
+        </div>
+      </div>
+    </el-col> 
+  </el-row>
+</template>
+<script>
+  export default {
+    data () {
+      return {
+        circleUrl: "https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png",
+        squareUrl: "https://cube.elemecdn.com/9/c2/f0ee8a3c7c9638a54940382568c9dpng.png",
+        sizeList: ["large", "medium", "small"]
+      }
+    }
+  }
+</script>
+
+```
+:::
+
+### 展示类型
+
+支持三种类型:图标、图片和字符
+
+:::demo
+```html
+<template>
+  <div class="demo-type">
+    <div>
+      <el-avatar icon="el-icon-user-solid"></el-avatar>
+    </div>
+    <div>
+      <el-avatar src="https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png"></el-avatar>
+    </div>
+    <div>
+      <el-avatar> user </el-avatar>
+    </div>
+  </div>
+</template>
+```
+:::
+
+### 图片加载失败的 fallback 行为
+
+当展示类型为图片的时候,图片加载失败的 fallback 行为
+
+:::demo
+```html
+<template>
+  <div class="demo-type">
+    <el-avatar :size="60" src="https://empty" @error="errorHandler">
+      <img src="https://cube.elemecdn.com/e/fd/0fc7d20532fdaf769a25683617711png.png"/>
+    </el-avatar>
+  </div>
+</template>
+<script>
+  export default {
+    methods: {
+      errorHandler() {
+        return true
+      }
+    }
+  }
+</script>
+
+```
+:::
+
+### 图片如何适应容器框
+
+当展示类型为图片的时候,使用 `fit` 属性定义图片如何适应容器框,同原生 [object-fit](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit)。
+
+:::demo
+```html
+<template>
+  <div class="demo-fit">
+    <div class="block" v-for="fit in fits" :key="fit">
+        <span class="title">{{ fit }}</span>
+        <el-avatar shape="square" :size="100" :fit="fit" :src="url"></el-avatar>
+    </div>
+  </div>
+</template>
+<script>
+  export default {
+    data() {
+      return {
+        fits: ['fill', 'contain', 'cover', 'none', 'scale-down'],
+        url: 'https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg'
+      }
+    }
+  }
+</script>
+
+```
+:::
+
+### Attributes
+
+| 参数              | 说明                             | 类型            | 可选值 | 默认值 |
+| ----------------- | -------------------------------- | --------------- | ------ | ------ |
+| icon              | 设置头像的图标类型,参考 Icon 组件   | string          |        |        |
+| size              | 设置头像的大小                     | number/string | number / large / medium / small | large  |
+| shape             | 设置头像的形状  | string |    circle / square     |   circle  |
+| src               | 图片头像的资源地址 | string |        |      |
+| srcSet            | 以逗号分隔的一个或多个字符串列表表明一系列用户代理使用的可能的图像 | string |        |      |
+| alt               | 描述图像的替换文本 | string |        |      |
+| fit               | 当展示类型为图片的时候,设置图片如何适应容器框 | string |    fill / contain / cover / none / scale-down    |   cover   |
+
+
+### Events
+
+| 事件名 | 说明               | 回调参数 |
+| ------ | ------------------ | -------- |
+| error  | 图片类头像加载失败的回调, 返回 false 会关闭组件默认的 fallback 行为 |(e: Event)  |
+
+### Slot
+
+| 名称	 | 说明               |  
+| ------ | ------------------ | 
+| default  | 自定义头像展示内容 |

+ 16 - 0
examples/nav.config.json

@@ -172,6 +172,10 @@
             {
               "path": "/badge",
               "title": "Badge 标记"
+            },
+            {
+              "path": "/avatar",
+              "title": "Avatar 头像"
             }
           ]
         },
@@ -565,6 +569,10 @@
             {
               "path": "/infiniteScroll",
               "title": "InfiniteScroll"
+            },
+            {
+              "path": "/avatar",
+              "title": "Avatar"
             }
           ]
         }
@@ -851,6 +859,10 @@
             {
               "path": "/infiniteScroll",
               "title": "InfiniteScroll"
+            },
+            {
+              "path": "/avatar",
+              "title": "Avatar"
             }
           ]
         }
@@ -1137,6 +1149,10 @@
             {
               "path": "/infiniteScroll",
               "title": "InfiniteScroll"
+            },
+            {
+              "path": "/avatar",
+              "title": "Avatar"
             }
           ]
         }

+ 8 - 0
packages/avatar/index.js

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

+ 107 - 0
packages/avatar/src/main.vue

@@ -0,0 +1,107 @@
+<script>
+export default {
+  name: 'ElAvatar',
+
+  props: {
+    size: {
+      type: [Number, String],
+      validator(val) {
+        if (typeof val === 'string') {
+          return ['large', 'medium', 'small'].includes(val);
+        }
+        return typeof val === 'number';
+      }
+    },
+    shape: {
+      type: String,
+      default: 'circle',
+      validator(val) {
+        return ['circle', 'square'].includes(val);
+      }
+    },
+    icon: String,
+    src: String,
+    alt: String,
+    srcSet: String,
+    error: Function,
+    fit: {
+      type: String,
+      default: 'cover'
+    }
+  },
+
+  data() {
+    return {
+      isImageExist: true
+    };
+  },
+
+  computed: {
+    avatarClass() {
+      const { size, icon, shape } = this;
+      let classList = ['el-avatar'];
+
+      if (size && typeof size === 'string') {
+        classList.push(`el-avatar--${size}`);
+      }
+
+      if (icon) {
+        classList.push('el-avatar--icon');
+      }
+
+      if (shape) {
+        classList.push(`el-avatar--${shape}`);
+      }
+
+      return classList.join(' ');
+    }
+  },
+
+  methods: {
+    handleError() {
+      const { error } = this;
+      const errorFlag = error ? error() : undefined;
+      if (errorFlag !== false) {
+        this.isImageExist = false;
+      }
+    },
+    renderAvatar() {
+      const { icon, src, alt, isImageExist, srcSet, fit } = this;
+
+      if (isImageExist && src) {
+        return <img
+          src={src}
+          onError={this.handleError}
+          alt={alt}
+          srcSet={srcSet}
+          style={{ 'object-fit': fit }}/>;
+      }
+
+      if (icon) {
+        return (<i class={icon} />);
+      }
+
+      return this.$slots.default;
+    }
+  },
+
+  render() {
+    const { avatarClass, size } = this;
+
+    const sizeStyle = typeof size === 'number' ? {
+      height: `${size}px`,
+      width: `${size}px`,
+      lineHeight: `${size}px`
+    } : {};
+
+    return (
+      <span class={ avatarClass } style={ sizeStyle }>
+        {
+          this.renderAvatar()
+        }
+      </span>
+    );
+  }
+
+};
+</script>

+ 49 - 0
packages/theme-chalk/src/avatar.scss

@@ -0,0 +1,49 @@
+@import "mixins/mixins";
+@import "common/var";
+
+@include b(avatar) {
+  display: inline-block;
+  box-sizing: border-box;
+  text-align: center;
+  overflow: hidden;
+  color: $--avatar-font-color;
+  background: $--avatar-background-color;
+  width: $--avatar-size;
+  height: $--avatar-size;
+  line-height: $--avatar-size;
+
+  >img {
+    width: 100%;
+    height: 100%;
+  }
+
+  @include m(circle) {
+    border-radius: 50%;
+  }
+
+  @include m(square) {
+    border-radius: 4px;
+  }
+
+  @include m(icon) {
+    font-size: 18px;
+  }
+
+  @include m(large) {
+    width: $--avatar-large-size;
+    height: $--avatar-large-size;
+    line-height: $--avatar-large-size;
+  }
+
+  @include m(medium) {
+    width: $--avatar-medium-size;
+    height: $--avatar-medium-size;
+    line-height: $--avatar-medium-size;
+  }
+
+  @include m(small) {
+    width: $--avatar-small-size;
+    height: $--avatar-small-size;
+    line-height: $--avatar-small-size;
+  }
+}

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

@@ -939,6 +939,17 @@ $--calendar-border: $--table-border !default;
 $--calendar-selected-background-color: #F2F8FE !default;
 $--calendar-cell-width: 85px !default;
 
+/* Avatar
+--------------------------*/
+/// color||Color|0
+$--avatar-font-color: #fff;
+/// color||Color|0
+$--avatar-background-color: #C0C4CC;
+$--avatar-size: 40px;
+$--avatar-large-size: $--avatar-size;
+$--avatar-medium-size: 36px;
+$--avatar-small-size: 28px;
+
 /* Break-point
 --------------------------*/
 $--sm: 768px !default;

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

@@ -75,3 +75,4 @@
 @import "./infinite-scroll.scss";
 @import "./page-header.scss";
 @import "./cascader-panel.scss";
+@import "./avatar.scss";

+ 4 - 1
src/index.js

@@ -79,6 +79,7 @@ import Backtop from '../packages/backtop/index.js';
 import InfiniteScroll from '../packages/infinite-scroll/index.js';
 import PageHeader from '../packages/page-header/index.js';
 import CascaderPanel from '../packages/cascader-panel/index.js';
+import Avatar from '../packages/avatar/index.js';
 import locale from 'element-ui/src/locale';
 import CollapseTransition from 'element-ui/src/transitions/collapse-transition';
 
@@ -157,6 +158,7 @@ const components = [
   Backtop,
   PageHeader,
   CascaderPanel,
+  Avatar,
   CollapseTransition
 ];
 
@@ -275,5 +277,6 @@ export default {
   Backtop,
   InfiniteScroll,
   PageHeader,
-  CascaderPanel
+  CascaderPanel,
+  Avatar
 };

+ 123 - 0
test/unit/specs/avatar.spec.js

@@ -0,0 +1,123 @@
+import {createTest, createVue, destroyVM} from '../util';
+import Avatar from 'packages/avatar';
+
+describe('Avatar', () => {
+  let vm;
+  afterEach(() => {
+    destroyVM(vm);
+  });
+
+  it('create', () => {
+    vm = createTest(Avatar);
+    expect(vm.$el).to.exist;
+  });
+
+  it('size is number', () => {
+    vm = createVue({
+      template: `
+        <el-avatar :size="50">
+        </el-avatar>
+      `
+    }, true);
+    const avatarElm = vm.$el;
+    expect(avatarElm.style.height).to.equal('50px');
+  });
+
+  it('size is string', () => {
+    vm = createVue({
+      template: `
+        <el-avatar size="small">
+          user
+        </el-avatar>
+      `
+    }, true);
+    const avatarElm = vm.$el;
+    expect(avatarElm.classList.contains('el-avatar--small')).to.be.true;
+  });
+
+  it('shape', () => {
+    vm = createVue({
+      template: `
+        <el-avatar size="small" shape="square">
+          user
+        </el-avatar>
+      `
+    }, true);
+    const avatarElm = vm.$el;
+    expect(avatarElm.classList.contains('el-avatar--square')).to.be.true;
+  });
+
+  it('icon avatar', () => {
+    vm = createVue({
+      template: `
+        <el-avatar icon="el-icon-user-solid">
+        </el-avatar>
+      `
+    }, true);
+    const avatarElm = vm.$el;
+    const iconELm = avatarElm.children[0];
+    expect(avatarElm.classList.contains('el-avatar--icon')).to.be.true;
+    expect(iconELm.classList.contains('el-icon-user-solid')).to.be.true;
+  });
+
+  it('image avatar', () => {
+    vm = createVue({
+      template: `
+        <el-avatar src="https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png"></el-avatar>
+      `
+    }, true);
+    const imgElm = vm.$el.children[0];
+    expect(imgElm.tagName.toUpperCase()).to.equal('IMG');
+    expect(imgElm.src).to.equal('https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png');
+  });
+
+  it('image fallback', (done) => {
+    vm = createVue({
+      template: `
+        <el-avatar src="https://empty" @error="errorHandler">
+          fallback
+        </el-avatar>
+      `,
+      methods: {
+        errorHandler() {
+          return true;
+        }
+      }
+    }, true);
+    setTimeout(() => {
+      const avatarElm = vm.$el;
+      expect(avatarElm.textContent.trim()).to.equal('fallback');
+      done();
+    }, 3000);
+  });
+
+  it('image fit', (done) => {
+    vm = createVue({
+      template: `
+        <div>
+          <el-avatar :src="url"></el-avatar>
+          <el-avatar :src="url" v-for="fit in fits" :fit="fit" :key="fit"></el-avatar>
+        </div>
+        
+      `,
+      data() {
+        return {
+          fits: ['fill', 'contain', 'cover', 'none', 'scale-down'],
+          url: 'https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg'
+        };
+      }
+    }, true);
+    setTimeout(() => {
+      const containerElm = vm.$el;
+      expect(containerElm.children[0].children[0].style.objectFit).to.equal('cover');
+      expect(containerElm.children[1].children[0].style.objectFit).to.equal('fill');
+      expect(containerElm.children[2].children[0].style.objectFit).to.equal('contain');
+      expect(containerElm.children[3].children[0].style.objectFit).to.equal('cover');
+      expect(containerElm.children[4].children[0].style.objectFit).to.equal('none');
+      expect(containerElm.children[5].children[0].style.objectFit).to.equal('scale-down');
+
+      done();
+    }, 3000);
+  });
+});
+

+ 20 - 0
types/avatar.d.ts

@@ -0,0 +1,20 @@
+import { ElementUIComponent } from './component'
+
+/** Avatar Component */
+export declare class ElAvatar extends ElementUIComponent {
+  icon: string;
+
+  size: string | number;
+
+  shape: string;
+
+  src: string;
+
+  error: () => false;
+
+  srcSet: string;
+
+  alt: string;
+
+  fit: string;
+}

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

@@ -77,6 +77,7 @@ import { ElImage } from './image'
 import { ElBacktop } from './backtop'
 import { ElInfiniteScroll } from './infiniteScroll'
 import { ElPageHeader } from './page-header'
+import { ElAvatar } from './avatar'
 
 export interface InstallationOptions {
   locale: any,
@@ -332,3 +333,6 @@ export const InfiniteScroll: PluginObject<ElInfiniteScroll>;
 
 /** PageHeader Component */
 export class PageHeader extends ElPageHeader {}
+
+/** Avatar Component */
+export class Avatar extends ElAvatar {}