Pārlūkot izejas kodu

Empty: add empty component (#21080)

好多大米 4 gadi atpakaļ
vecāks
revīzija
d0b3454032
68 mainītis faili ar 787 papildinājumiem un 2 dzēšanām
  1. 2 1
      components.json
  2. 61 0
      examples/docs/en-US/empty.md
  3. 61 0
      examples/docs/es/empty.md
  4. 61 0
      examples/docs/fr-FR/empty.md
  5. 61 0
      examples/docs/zh-CN/empty.md
  6. 16 0
      examples/nav.config.json
  7. 7 0
      packages/empty/index.js
  8. 132 0
      packages/empty/src/img-empty.vue
  9. 50 0
      packages/empty/src/index.vue
  10. 6 0
      packages/theme-chalk/src/common/var.scss
  11. 45 0
      packages/theme-chalk/src/empty.scss
  12. 1 0
      packages/theme-chalk/src/index.scss
  13. 4 1
      src/index.js
  14. 3 0
      src/locale/lang/af-ZA.js
  15. 3 0
      src/locale/lang/ar.js
  16. 3 0
      src/locale/lang/bg.js
  17. 3 0
      src/locale/lang/ca.js
  18. 3 0
      src/locale/lang/cs-CZ.js
  19. 3 0
      src/locale/lang/da.js
  20. 3 0
      src/locale/lang/de.js
  21. 3 0
      src/locale/lang/ee.js
  22. 3 0
      src/locale/lang/el.js
  23. 3 0
      src/locale/lang/en.js
  24. 3 0
      src/locale/lang/eo.js
  25. 3 0
      src/locale/lang/es.js
  26. 3 0
      src/locale/lang/eu.js
  27. 3 0
      src/locale/lang/fa.js
  28. 3 0
      src/locale/lang/fi.js
  29. 3 0
      src/locale/lang/fr.js
  30. 3 0
      src/locale/lang/he.js
  31. 3 0
      src/locale/lang/hr.js
  32. 3 0
      src/locale/lang/hu.js
  33. 3 0
      src/locale/lang/hy-AM.js
  34. 3 0
      src/locale/lang/id.js
  35. 3 0
      src/locale/lang/it.js
  36. 3 0
      src/locale/lang/ja.js
  37. 3 0
      src/locale/lang/kg.js
  38. 3 0
      src/locale/lang/km.js
  39. 3 0
      src/locale/lang/ko.js
  40. 3 0
      src/locale/lang/ku.js
  41. 3 0
      src/locale/lang/kz.js
  42. 3 0
      src/locale/lang/lt.js
  43. 3 0
      src/locale/lang/lv.js
  44. 3 0
      src/locale/lang/mn.js
  45. 3 0
      src/locale/lang/nb-NO.js
  46. 3 0
      src/locale/lang/nl.js
  47. 3 0
      src/locale/lang/pl.js
  48. 3 0
      src/locale/lang/pt-br.js
  49. 3 0
      src/locale/lang/pt.js
  50. 3 0
      src/locale/lang/ro.js
  51. 3 0
      src/locale/lang/ru-RU.js
  52. 3 0
      src/locale/lang/sk.js
  53. 3 0
      src/locale/lang/sl.js
  54. 3 0
      src/locale/lang/sr.js
  55. 3 0
      src/locale/lang/sv-SE.js
  56. 3 0
      src/locale/lang/ta.js
  57. 3 0
      src/locale/lang/th.js
  58. 3 0
      src/locale/lang/tk.js
  59. 3 0
      src/locale/lang/tr-TR.js
  60. 3 0
      src/locale/lang/ua.js
  61. 3 0
      src/locale/lang/ug-CN.js
  62. 3 0
      src/locale/lang/uz-UZ.js
  63. 3 0
      src/locale/lang/vi.js
  64. 3 0
      src/locale/lang/zh-CN.js
  65. 3 0
      src/locale/lang/zh-TW.js
  66. 89 0
      test/unit/specs/empty.spec.js
  67. 4 0
      types/element-ui.d.ts
  68. 31 0
      types/empty.d.ts

+ 2 - 1
components.json

@@ -82,5 +82,6 @@
   "drawer": "./packages/drawer/index.js",
   "popconfirm": "./packages/popconfirm/index.js",
   "skeleton": "./packages/skeleton/index.js",
-  "skeleton-item": "./packages/skeleton-item/index.js"
+  "skeleton-item": "./packages/skeleton-item/index.js",
+  "empty": "./packages/empty/index.js"
 }

+ 61 - 0
examples/docs/en-US/empty.md

@@ -0,0 +1,61 @@
+## Empty
+
+Placeholder hints for empty states.
+
+### Basic usage
+
+:::demo
+
+```html
+<el-empty description="description"></el-empty>
+```
+:::
+
+### Custom image
+
+Use `image` prop to set image URL.
+
+:::demo
+
+```html
+<el-empty image="https://shadow.elemecdn.com/app/element/hamburger.9cf7b091-55e9-11e9-a976-7f4d0b07eef6.png"></el-empty>
+```
+:::
+
+### Image size
+
+Use `image-size` prop to control image size.
+
+:::demo
+
+```html
+<el-empty :image-size="200"></el-empty>
+```
+:::
+
+### Bottom content
+
+Use the default slot to insert content at the bottom.
+
+:::demo
+```html
+<el-empty>
+  <el-button type="primary">Button</el-button>
+</el-empty>
+```
+:::
+
+### Empty Attributes
+| Attribute       | Description      | Type         | Acceptable Value    | Default   |
+|-------------  |---------------- |---------------- |---------------------- |-------- |
+| image          | image URL       | string  |          —             |    —     |
+| image-size    | image size (width)  | number | — |    —  |
+| description  | description    | string  |    —  |  — |
+
+### Empty Slots
+
+| Name | Description |
+|------|--------|
+| default | Custom bottom content  |
+| image | Custom image     |
+| description | Custom description     |

+ 61 - 0
examples/docs/es/empty.md

@@ -0,0 +1,61 @@
+## Empty
+
+Placeholder hints for empty states.
+
+### Basic usage
+
+:::demo
+
+```html
+<el-empty description="descrição"></el-empty>
+```
+:::
+
+### Custom image
+
+Use `image` prop to set image URL.
+
+:::demo
+
+```html
+<el-empty image="https://shadow.elemecdn.com/app/element/hamburger.9cf7b091-55e9-11e9-a976-7f4d0b07eef6.png"></el-empty>
+```
+:::
+
+### Image size
+
+Use `image-size` prop to control image size.
+
+:::demo
+
+```html
+<el-empty :image-size="200"></el-empty>
+```
+:::
+
+### Bottom content
+
+Use the default slot to insert content at the bottom.
+
+:::demo
+```html
+<el-empty>
+  <el-button type="primary">Button</el-button>
+</el-empty>
+```
+:::
+
+### Empty Attributes
+| Attribute       | Description      | Type         | Acceptable Value    | Default Value   |
+|-------------  |---------------- |---------------- |---------------------- |-------- |
+| image          | image URL       | string  |          —             |    —     |
+| image-size    | image size (width)  | number | — |    —  |
+| description  | description    | string  |    —  |  — |
+
+### Empty Slots
+
+| Name | Description |
+|------|--------|
+| default | Custom bottom content  |
+| image | Custom image     |
+| description | Custom description     |

+ 61 - 0
examples/docs/fr-FR/empty.md

@@ -0,0 +1,61 @@
+## Empty
+
+Placeholder hints for empty states.
+
+### Basic usage
+
+:::demo
+
+```html
+<el-empty description="description"></el-empty>
+```
+:::
+
+### Custom image
+
+Use `image` prop to set image URL.
+
+:::demo
+
+```html
+<el-empty image="https://shadow.elemecdn.com/app/element/hamburger.9cf7b091-55e9-11e9-a976-7f4d0b07eef6.png"></el-empty>
+```
+:::
+
+### Image size
+
+Use `image-size` prop to control image size.
+
+:::demo
+
+```html
+<el-empty :image-size="200"></el-empty>
+```
+:::
+
+### Bottom content
+
+Use the default slot to insert content at the bottom.
+
+:::demo
+```html
+<el-empty>
+  <el-button type="primary">Button</el-button>
+</el-empty>
+```
+:::
+
+### Empty Attributes
+| Attribute       | Description      | Type         | Acceptable Value    | Default Value   |
+|-------------  |---------------- |---------------- |---------------------- |-------- |
+| image          | image URL       | string  |          —             |    —     |
+| image-size    | image size (width)  | number | — |    —  |
+| description  | description    | string  |    —  |  — |
+
+### Empty Slots
+
+| Name | Description |
+|------|--------|
+| default | Custom bottom content  |
+| image | Custom image     |
+| description | Custom description     |

+ 61 - 0
examples/docs/zh-CN/empty.md

@@ -0,0 +1,61 @@
+## Empty 空状态
+
+空状态时的占位提示。
+
+### 基础用法
+
+:::demo
+
+```html
+<el-empty description="描述文字"></el-empty>
+```
+:::
+
+### 自定义图片
+
+通过设置 `image` 属性传入图片 URL。
+
+:::demo
+
+```html
+<el-empty image="https://shadow.elemecdn.com/app/element/hamburger.9cf7b091-55e9-11e9-a976-7f4d0b07eef6.png"></el-empty>
+```
+:::
+
+### 图片尺寸
+
+通过设置 `image-size` 属性来控制图片大小。
+
+:::demo
+
+```html
+<el-empty :image-size="200"></el-empty>
+```
+:::
+
+### 底部内容
+
+使用默认插槽可在底部插入内容。
+
+:::demo
+```html
+<el-empty>
+  <el-button type="primary">按钮</el-button>
+</el-empty>
+```
+:::
+
+### Empty Attributes
+| 参数          | 说明            | 类型            | 可选值                 | 默认值   |
+|-------------  |---------------- |---------------- |---------------------- |-------- |
+| image          | 图片地址         | string  |          —             |    —     |
+| image-size    | 图片大小(宽度)  | number | — |    —  |
+| description  | 文本描述    | string  |    —  |  — |
+
+### Empty Slots
+
+| Name | 说明 |
+|------|--------|
+| default | 自定义底部内容  |
+| image | 自定义图片     |
+| description | 自定义描述文字     |

+ 16 - 0
examples/nav.config.json

@@ -180,6 +180,10 @@
             {
               "path": "/skeleton",
               "title": "Skeleton 骨架屏"
+            },
+            {
+              "path": "/empty",
+              "title": "Empty 空状态"
             }
           ]
         },
@@ -478,6 +482,10 @@
             {
               "path": "/skeleton",
               "title": "Skeleton"
+            },
+            {
+              "path": "/empty",
+              "title": "Empty"
             }
           ]
         },
@@ -780,6 +788,10 @@
             {
               "path": "/skeleton",
               "title": "Skeleton"
+            },
+            {
+              "path": "/empty",
+              "title": "Empty"
             }
           ]
         },
@@ -1082,6 +1094,10 @@
             {
               "path": "/skeleton",
               "title": "Skeleton"
+            },
+            {
+              "path": "/empty",
+              "title": "Empty"
             }
           ]
         },

+ 7 - 0
packages/empty/index.js

@@ -0,0 +1,7 @@
+import Empty from './src/index.vue';
+
+Empty.install = (Vue) => {
+  Vue.component(Empty.name, Empty);
+};
+
+export default Empty;

+ 132 - 0
packages/empty/src/img-empty.vue

@@ -0,0 +1,132 @@
+<template>
+  <svg
+    viewBox="0 0 79 86"
+    version="1.1"
+    xmlns="http://www.w3.org/2000/svg"
+    xmlns:xlink="http://www.w3.org/1999/xlink"
+  >
+    <defs>
+      <linearGradient
+        :id="`linearGradient-1-${id}`"
+        x1="38.8503086%"
+        y1="0%"
+        x2="61.1496914%"
+        y2="100%"
+      >
+        <stop stop-color="#FCFCFD" offset="0%" />
+        <stop stop-color="#EEEFF3" offset="100%" />
+      </linearGradient>
+      <linearGradient
+        :id="`linearGradient-2-${id}`"
+        x1="0%"
+        y1="9.5%"
+        x2="100%"
+        y2="90.5%"
+      >
+        <stop stop-color="#FCFCFD" offset="0%" />
+        <stop stop-color="#E9EBEF" offset="100%" />
+      </linearGradient>
+      <rect
+        :id="`path-3-${id}`"
+        x="0"
+        y="0"
+        width="17"
+        height="36"
+      />
+    </defs>
+    <g
+      id="Illustrations"
+      stroke="none"
+      stroke-width="1"
+      fill="none"
+      fill-rule="evenodd"
+    >
+      <g id="B-type" transform="translate(-1268.000000, -535.000000)">
+        <g id="Group-2" transform="translate(1268.000000, 535.000000)">
+          <path
+            id="Oval-Copy-2"
+            d="M39.5,86 C61.3152476,86 79,83.9106622 79,81.3333333 C79,78.7560045 57.3152476,78 35.5,78 C13.6847524,78 0,78.7560045 0,81.3333333 C0,83.9106622 17.6847524,86 39.5,86 Z"
+            fill="#F7F8FC"
+          />
+          <polygon
+            id="Rectangle-Copy-14"
+            fill="#E5E7E9"
+            transform="translate(27.500000, 51.500000) scale(1, -1) translate(-27.500000, -51.500000) "
+            points="13 58 53 58 42 45 2 45"
+          />
+          <g
+            id="Group-Copy"
+            transform="translate(34.500000, 31.500000) scale(-1, 1) rotate(-25.000000) translate(-34.500000, -31.500000) translate(7.000000, 10.000000)"
+          >
+            <polygon
+              id="Rectangle-Copy-10"
+              fill="#E5E7E9"
+              transform="translate(11.500000, 5.000000) scale(1, -1) translate(-11.500000, -5.000000) "
+              points="2.84078316e-14 3 18 3 23 7 5 7"
+            />
+            <polygon id="Rectangle-Copy-11" fill="#EDEEF2" points="-3.69149156e-15 7 38 7 38 43 -3.69149156e-15 43" />
+            <rect
+              id="Rectangle-Copy-12"
+              :fill="`url(#linearGradient-1-${id})`"
+              transform="translate(46.500000, 25.000000) scale(-1, 1) translate(-46.500000, -25.000000) "
+              x="38"
+              y="7"
+              width="17"
+              height="36"
+            />
+            <polygon
+              id="Rectangle-Copy-13"
+              fill="#F8F9FB"
+              transform="translate(39.500000, 3.500000) scale(-1, 1) translate(-39.500000, -3.500000) "
+              points="24 7 41 7 55 -3.63806207e-12 38 -3.63806207e-12"
+            />
+          </g>
+          <rect
+            id="Rectangle-Copy-15"
+            :fill="`url(#linearGradient-2-${id})`"
+            x="13"
+            y="45"
+            width="40"
+            height="36"
+          />
+          <g id="Rectangle-Copy-17" transform="translate(53.000000, 45.000000)">
+            <mask :id="`mask-4-${id}`" fill="white">
+              <use :xlink:href="`#path-3-${id}`" />
+            </mask>
+            <use
+              id="Mask"
+              fill="#E0E3E9"
+              transform="translate(8.500000, 18.000000) scale(-1, 1) translate(-8.500000, -18.000000) "
+              :xlink:href="`#path-3-${id}`"
+            />
+            <polygon
+              id="Rectangle-Copy"
+              fill="#D5D7DE"
+              :mask="`url(#mask-4-${id})`"
+              transform="translate(12.000000, 9.000000) scale(-1, 1) translate(-12.000000, -9.000000) "
+              points="7 0 24 0 20 18 -1.70530257e-13 16"
+            />
+          </g>
+          <polygon
+            id="Rectangle-Copy-18"
+            fill="#F8F9FB"
+            transform="translate(66.000000, 51.500000) scale(-1, 1) translate(-66.000000, -51.500000) "
+            points="62 45 79 45 70 58 53 58"
+          />
+        </g>
+      </g>
+    </g>
+  </svg>
+</template>
+
+<script>
+let id = 0;
+export default {
+  name: 'ImgEmpty',
+  data() {
+    return {
+      id: ++id
+    };
+  }
+};
+</script>

+ 50 - 0
packages/empty/src/index.vue

@@ -0,0 +1,50 @@
+<template>
+  <div class="el-empty">
+    <div class="el-empty__image" :style="imageStyle">
+      <img v-if="image" :src="image" ondragstart="return false">
+      <slot v-else name="image">
+        <img-empty />
+      </slot>
+    </div>
+    <div class="el-empty__description">
+      <slot v-if="$slots.description" name="description"></slot>
+      <p v-else>{{ emptyDescription }}</p>
+    </div>
+    <div v-if="$slots.default" class="el-empty__bottom">
+      <slot></slot>
+    </div>
+  </div>
+</template>
+
+<script>
+import ImgEmpty from './img-empty.vue';
+import { t } from 'element-ui/src/locale';
+
+export default {
+  name: 'ElEmpty',
+  components: {
+    [ImgEmpty.name]: ImgEmpty
+  },
+  props: {
+    image: {
+      type: String,
+      default: ''
+    },
+    imageSize: Number,
+    description: {
+      type: String,
+      default: ''
+    }
+  },
+  computed: {
+    emptyDescription() {
+      return this.description || t('el.empty.description');
+    },
+    imageStyle() {
+      return {
+        width: this.imageSize ? `${this.imageSize}px` : ''
+      };
+    }
+  }
+};
+</script>

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

@@ -964,6 +964,12 @@ $--avatar-medium-size: 36px !default;
 /// size|1|Avatar Size|3
 $--avatar-small-size: 28px !default;
 
+/* Empty
+-------------------------- */
+$--empty-padding: 40px 0 !default;
+$--empty-image-width: 160px !default;
+$--empty-description-margin-top: 20px !default;
+$--empty-bottom-margin-top: 20px !default;
 
 /* Skeleton 
 --------------------------*/

+ 45 - 0
packages/theme-chalk/src/empty.scss

@@ -0,0 +1,45 @@
+@import "mixins/mixins";
+@import "common/var";
+
+@include b(empty) {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  flex-direction: column;
+  text-align: center;
+  box-sizing: border-box;
+  padding: $--empty-padding;
+
+  @include e(image) {
+    width: $--empty-image-width;
+
+    img {
+      user-select: none;
+      width: 100%;
+      height: 100%;
+      vertical-align: top;
+      object-fit: contain;
+    }
+
+    svg {
+      fill: $--svg-monochrome-grey;
+      width: 100%;
+      height: 100%;
+      vertical-align: top;
+    }
+  }
+
+  @include e(description) {
+    margin-top: $--empty-description-margin-top;
+
+    p {
+      margin: 0;
+      font-size: $--font-size-base;
+      color: $--color-text-secondary;
+    }
+  }
+
+  @include e(bottom) {
+    margin-top: $--empty-bottom-margin-top;
+  }
+}

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

@@ -80,3 +80,4 @@
 @import "./popconfirm.scss";
 @import "./skeleton.scss";
 @import "./skeleton-item.scss";
+@import "./empty.scss";

+ 4 - 1
src/index.js

@@ -84,6 +84,7 @@ import Drawer from '../packages/drawer/index.js';
 import Popconfirm from '../packages/popconfirm/index.js';
 import Skeleton from '../packages/skeleton/index.js';
 import SkeletonItem from '../packages/skeleton-item/index.js';
+import Empty from '../packages/empty/index.js';
 import locale from 'element-ui/src/locale';
 import CollapseTransition from 'element-ui/src/transitions/collapse-transition';
 
@@ -167,6 +168,7 @@ const components = [
   Popconfirm,
   Skeleton,
   SkeletonItem,
+  Empty,
   CollapseTransition
 ];
 
@@ -290,5 +292,6 @@ export default {
   Drawer,
   Popconfirm,
   Skeleton,
-  SkeletonItem
+  SkeletonItem,
+  Empty
 };

+ 3 - 0
src/locale/lang/af-ZA.js

@@ -115,6 +115,9 @@ export default {
     popconfirm: {
       confirmButtonText: 'Yes', // to be translated
       cancelButtonText: 'No' // to be translated
+    },
+    empty: {
+      description: 'Geen Data'
     }
   }
 };

+ 3 - 0
src/locale/lang/ar.js

@@ -114,6 +114,9 @@ export default {
     popconfirm: {
       confirmButtonText: 'تأكيد',
       cancelButtonText: 'إلغاء'
+    },
+    empty: {
+      description: 'لايوجد بيانات'
     }
   }
 };

+ 3 - 0
src/locale/lang/bg.js

@@ -115,6 +115,9 @@ export default {
     popconfirm: {
       confirmButtonText: 'Yes', // to be translated
       cancelButtonText: 'No' // to be translated
+    },
+    empty: {
+      description: 'Няма данни'
     }
   }
 };

+ 3 - 0
src/locale/lang/ca.js

@@ -114,6 +114,9 @@ export default {
     popconfirm: {
       confirmButtonText: 'Si',
       cancelButtonText: 'No'
+    },
+    empty: {
+      description: 'Sense Dades'
     }
   }
 };

+ 3 - 0
src/locale/lang/cs-CZ.js

@@ -117,6 +117,9 @@ export default {
     popconfirm: {
       confirmButtonText: 'Yes', // to be translated
       cancelButtonText: 'No' // to be translated
+    },
+    empty: {
+      description: 'Žádná data'
     }
   }
 };

+ 3 - 0
src/locale/lang/da.js

@@ -114,6 +114,9 @@ export default {
     popconfirm: {
       confirmButtonText: 'Yes', // to be translated
       cancelButtonText: 'No' // to be translated
+    },
+    empty: {
+      description: 'Ingen data'
     }
   }
 };

+ 3 - 0
src/locale/lang/de.js

@@ -116,6 +116,9 @@ export default {
     popconfirm: {
       confirmButtonText: 'Yes', // to be translated
       cancelButtonText: 'No' // to be translated
+    },
+    empty: {
+      description: 'Keine Daten'
     }
   }
 };

+ 3 - 0
src/locale/lang/ee.js

@@ -115,6 +115,9 @@ export default {
     popconfirm: {
       confirmButtonText: 'Yes', // to be translated
       cancelButtonText: 'No' // to be translated
+    },
+    empty: {
+      description: 'Andmed puuduvad'
     }
   }
 };

+ 3 - 0
src/locale/lang/el.js

@@ -115,6 +115,9 @@ export default {
     popconfirm: {
       confirmButtonText: 'Yes', // to be translated
       cancelButtonText: 'No' // to be translated
+    },
+    empty: {
+      description: 'Χωρίς Δεδομένα'
     }
   }
 };

+ 3 - 0
src/locale/lang/en.js

@@ -115,6 +115,9 @@ export default {
     popconfirm: {
       confirmButtonText: 'Yes',
       cancelButtonText: 'No'
+    },
+    empty: {
+      description: 'No Data'
     }
   }
 };

+ 3 - 0
src/locale/lang/eo.js

@@ -115,6 +115,9 @@ export default {
     popconfirm: {
       confirmButtonText: 'Yes', // to be translated
       cancelButtonText: 'No' // to be translated
+    },
+    empty: {
+      description: 'Neniuj datumoj'
     }
   }
 };

+ 3 - 0
src/locale/lang/es.js

@@ -114,6 +114,9 @@ export default {
     popconfirm: {
       confirmButtonText: 'Si',
       cancelButtonText: 'No'
+    },
+    empty: {
+      description: 'Sin Datos'
     }
   }
 };

+ 3 - 0
src/locale/lang/eu.js

@@ -115,6 +115,9 @@ export default {
     popconfirm: {
       confirmButtonText: 'Yes', // to be translated
       cancelButtonText: 'No' // to be translated
+    },
+    empty: {
+      description: 'Daturik ez'
     }
   }
 };

+ 3 - 0
src/locale/lang/fa.js

@@ -115,6 +115,9 @@ export default {
     popconfirm: {
       confirmButtonText: 'Yes', // to be translated
       cancelButtonText: 'No' // to be translated
+    },
+    empty: {
+      description: 'اطلاعاتی وجود ندارد'
     }
   }
 };

+ 3 - 0
src/locale/lang/fi.js

@@ -115,6 +115,9 @@ export default {
     popconfirm: {
       confirmButtonText: 'Yes', // to be translated
       cancelButtonText: 'No' // to be translated
+    },
+    empty: {
+      description: 'Ei tietoja'
     }
   }
 };

+ 3 - 0
src/locale/lang/fr.js

@@ -114,6 +114,9 @@ export default {
     popconfirm: {
       confirmButtonText: 'Yes', // to be translated
       cancelButtonText: 'No' // to be translated
+    },
+    empty: {
+      description: 'Aucune donnée'
     }
   }
 };

+ 3 - 0
src/locale/lang/he.js

@@ -115,6 +115,9 @@ export default {
     popconfirm: {
       confirmButtonText: 'Yes', // to be translated
       cancelButtonText: 'No' // to be translated
+    },
+    empty: {
+      description: 'אין נתונים'
     }
   }
 };

+ 3 - 0
src/locale/lang/hr.js

@@ -115,6 +115,9 @@ export default {
     popconfirm: {
       confirmButtonText: 'Yes', // to be translated
       cancelButtonText: 'No' // to be translated
+    },
+    empty: {
+      description: 'Nema podataka'
     }
   }
 };

+ 3 - 0
src/locale/lang/hu.js

@@ -114,6 +114,9 @@ export default {
     popconfirm: {
       confirmButtonText: 'Yes', // to be translated
       cancelButtonText: 'No' // to be translated
+    },
+    empty: {
+      description: 'Nincs adat'
     }
   }
 };

+ 3 - 0
src/locale/lang/hy-AM.js

@@ -115,6 +115,9 @@ export default {
     popconfirm: {
       confirmButtonText: 'Yes', // to be translated
       cancelButtonText: 'No' // to be translated
+    },
+    empty: {
+      description: 'Տուեալներ չկան'
     }
   }
 };

+ 3 - 0
src/locale/lang/id.js

@@ -115,6 +115,9 @@ export default {
     popconfirm: {
       confirmButtonText: 'Ya',
       cancelButtonText: 'Tidak'
+    },
+    empty: {
+      description: 'Tidak ada data'
     }
   }
 };

+ 3 - 0
src/locale/lang/it.js

@@ -114,6 +114,9 @@ export default {
     popconfirm: {
       confirmButtonText: 'Yes', // to be translated
       cancelButtonText: 'No' // to be translated
+    },
+    empty: {
+      description: 'Nessun dato'
     }
   }
 };

+ 3 - 0
src/locale/lang/ja.js

@@ -115,6 +115,9 @@ export default {
     popconfirm: {
       confirmButtonText: 'Yes', // to be translated
       cancelButtonText: 'No' // to be translated
+    },
+    empty: {
+      description: 'データなし'
     }
   }
 };

+ 3 - 0
src/locale/lang/kg.js

@@ -115,6 +115,9 @@ export default {
     popconfirm: {
       confirmButtonText: 'Yes', // to be translated
       cancelButtonText: 'No' // to be translated
+    },
+    empty: {
+      description: 'маалымат жок'
     }
   }
 };

+ 3 - 0
src/locale/lang/km.js

@@ -115,6 +115,9 @@ export default {
     popconfirm: {
       confirmButtonText: 'ព្រម',
       cancelButtonText: 'មិនព្រម'
+    },
+    empty: {
+      description: 'គ្មានទិន្ន័យ'
     }
   }
 };

+ 3 - 0
src/locale/lang/ko.js

@@ -115,6 +115,9 @@ export default {
     popconfirm: {
       confirmButtonText: 'Yes', // to be translated
       cancelButtonText: 'No' // to be translated
+    },
+    empty: {
+      description: '데이터 없음'
     }
   }
 };

+ 3 - 0
src/locale/lang/ku.js

@@ -115,6 +115,9 @@ export default {
     popconfirm: {
       confirmButtonText: 'Yes', // to be translated
       cancelButtonText: 'No' // to be translated
+    },
+    empty: {
+      description: 'Agahî tune'
     }
   }
 };

+ 3 - 0
src/locale/lang/kz.js

@@ -115,6 +115,9 @@ export default {
     popconfirm: {
       confirmButtonText: 'Yes', // to be translated
       cancelButtonText: 'No' // to be translated
+    },
+    empty: {
+      description: 'Деректер жоқ'
     }
   }
 };

+ 3 - 0
src/locale/lang/lt.js

@@ -115,6 +115,9 @@ export default {
     popconfirm: {
       confirmButtonText: 'Yes', // to be translated
       cancelButtonText: 'No' // to be translated
+    },
+    empty: {
+      description: 'Duomenų nerasta'
     }
   }
 };

+ 3 - 0
src/locale/lang/lv.js

@@ -115,6 +115,9 @@ export default {
     popconfirm: {
       confirmButtonText: 'Yes', // to be translated
       cancelButtonText: 'No' // to be translated
+    },
+    empty: {
+      description: 'Nav datu'
     }
   }
 };

+ 3 - 0
src/locale/lang/mn.js

@@ -115,6 +115,9 @@ export default {
     popconfirm: {
       confirmButtonText: 'Yes', // to be translated
       cancelButtonText: 'No' // to be translated
+    },
+    empty: {
+      description: 'Өгөгдөл байхгүй'
     }
   }
 };

+ 3 - 0
src/locale/lang/nb-NO.js

@@ -114,6 +114,9 @@ export default {
     popconfirm: {
       confirmButtonText: 'Yes', // to be translated
       cancelButtonText: 'No' // to be translated
+    },
+    empty: {
+      description: 'Ingen Data'
     }
   }
 };

+ 3 - 0
src/locale/lang/nl.js

@@ -115,6 +115,9 @@ export default {
     popconfirm: {
       confirmButtonText: 'Yes', // to be translated
       cancelButtonText: 'No' // to be translated
+    },
+    empty: {
+      description: 'Geen data'
     }
   }
 };

+ 3 - 0
src/locale/lang/pl.js

@@ -115,6 +115,9 @@ export default {
     popconfirm: {
       confirmButtonText: 'Yes', // to be translated
       cancelButtonText: 'No' // to be translated
+    },
+    empty: {
+      description: 'Brak danych'
     }
   }
 };

+ 3 - 0
src/locale/lang/pt-br.js

@@ -115,6 +115,9 @@ export default {
     popconfirm: {
       confirmButtonText: 'Sim',
       cancelButtonText: 'Não'
+    },
+    empty: {
+      description: 'Sem dados'
     }
   }
 };

+ 3 - 0
src/locale/lang/pt.js

@@ -115,6 +115,9 @@ export default {
     popconfirm: {
       confirmButtonText: 'Yes', // to be translated
       cancelButtonText: 'No' // to be translated
+    },
+    empty: {
+      description: 'Sem dados'
     }
   }
 };

+ 3 - 0
src/locale/lang/ro.js

@@ -115,6 +115,9 @@ export default {
     popconfirm: {
       confirmButtonText: 'Yes', // to be translated
       cancelButtonText: 'No' // to be translated
+    },
+    empty: {
+      description: 'Nu există date'
     }
   }
 };

+ 3 - 0
src/locale/lang/ru-RU.js

@@ -115,6 +115,9 @@ export default {
     popconfirm: {
       confirmButtonText: 'OK',
       cancelButtonText: 'Отмена'
+    },
+    empty: {
+      description: 'Нет данных'
     }
   }
 };

+ 3 - 0
src/locale/lang/sk.js

@@ -117,6 +117,9 @@ export default {
     popconfirm: {
       confirmButtonText: 'Yes', // to be translated
       cancelButtonText: 'No' // to be translated
+    },
+    empty: {
+      description: 'Žiadne dáta'
     }
   }
 };

+ 3 - 0
src/locale/lang/sl.js

@@ -115,6 +115,9 @@ export default {
     popconfirm: {
       confirmButtonText: 'Yes', // to be translated
       cancelButtonText: 'No' // to be translated
+    },
+    empty: {
+      description: 'Ni podatkov'
     }
   }
 };

+ 3 - 0
src/locale/lang/sr.js

@@ -115,6 +115,9 @@ export default {
     popconfirm: {
       confirmButtonText: 'Yes', // to be translated
       cancelButtonText: 'No' // to be translated
+    },
+    empty: {
+      description: 'Нема података'
     }
   }
 };

+ 3 - 0
src/locale/lang/sv-SE.js

@@ -115,6 +115,9 @@ export default {
     popconfirm: {
       confirmButtonText: 'Ja',
       cancelButtonText: 'Nej'
+    },
+    empty: {
+      description: 'Inga Data'
     }
   }
 };

+ 3 - 0
src/locale/lang/ta.js

@@ -114,6 +114,9 @@ export default {
     popconfirm: {
       confirmButtonText: 'Yes', // to be translated
       cancelButtonText: 'No' // to be translated
+    },
+    empty: {
+      description: 'தரவு இல்லை'
     }
   }
 };

+ 3 - 0
src/locale/lang/th.js

@@ -115,6 +115,9 @@ export default {
     popconfirm: {
       confirmButtonText: 'Yes', // to be translated
       cancelButtonText: 'No' // to be translated
+    },
+    empty: {
+      description: 'ไม่พบข้อมูล'
     }
   }
 };

+ 3 - 0
src/locale/lang/tk.js

@@ -115,6 +115,9 @@ export default {
     popconfirm: {
       confirmButtonText: 'Yes', // to be translated
       cancelButtonText: 'No' // to be translated
+    },
+    empty: {
+      description: 'Maglumat ýok'
     }
   }
 };

+ 3 - 0
src/locale/lang/tr-TR.js

@@ -115,6 +115,9 @@ export default {
     popconfirm: {
       confirmButtonText: 'Yes', // to be translated
       cancelButtonText: 'No' // to be translated
+    },
+    empty: {
+      description: 'Veri yok'
     }
   }
 };

+ 3 - 0
src/locale/lang/ua.js

@@ -115,6 +115,9 @@ export default {
     popconfirm: {
       confirmButtonText: 'Так',
       cancelButtonText: 'Ні'
+    },
+    empty: {
+      description: 'Немає даних'
     }
   }
 };

+ 3 - 0
src/locale/lang/ug-CN.js

@@ -115,6 +115,9 @@ export default {
     popconfirm: {
       confirmButtonText: 'ھەئە',
       cancelButtonText: 'ياق'
+    },
+    empty: {
+      description: 'ئۇچۇر يوق'
     }
   }
 };

+ 3 - 0
src/locale/lang/uz-UZ.js

@@ -115,6 +115,9 @@ export default {
     popconfirm: {
       confirmButtonText: 'Yes', // to be translated
       cancelButtonText: 'No' // to be translated
+    },
+    empty: {
+      description: 'Boʻsh'
     }
   }
 };

+ 3 - 0
src/locale/lang/vi.js

@@ -115,6 +115,9 @@ export default {
     popconfirm: {
       confirmButtonText: 'Ok',
       cancelButtonText: 'Huỷ'
+    },
+    empty: {
+      description: 'Không có dữ liệu'
     }
   }
 };

+ 3 - 0
src/locale/lang/zh-CN.js

@@ -115,6 +115,9 @@ export default {
     popconfirm: {
       confirmButtonText: '确定',
       cancelButtonText: '取消'
+    },
+    empty: {
+      description: '暂无数据'
     }
   }
 };

+ 3 - 0
src/locale/lang/zh-TW.js

@@ -115,6 +115,9 @@ export default {
     popconfirm: {
       confirmButtonText: 'Yes', // to be translated
       cancelButtonText: 'No' // to be translated
+    },
+    empty: {
+      description: '暫無資料'
     }
   }
 };

+ 89 - 0
test/unit/specs/empty.spec.js

@@ -0,0 +1,89 @@
+import { createVue, createTest, destroyVM, waitImmediate } from '../util';
+import Empty from 'packages/empty';
+
+const AXIOM = 'Rem is the best girl';
+
+describe('Empty', () => {
+  let vm;
+  afterEach(() => {
+    destroyVM(vm);
+  });
+  it('render test', () => {
+    vm = createVue({
+      template: '<el-empty>{{ AXIOM }}</el-empty>',
+      data() {
+        return {
+          AXIOM
+        };
+      }
+    }, true);
+    expect(vm.$el.querySelector('.el-empty__image')).to.exist;
+    expect(vm.$el.querySelector('.el-empty__description')).to.exist;
+    expect(vm.$el.querySelector('.el-empty__bottom')).to.exist;
+  });
+
+  it('should render image props', () => {
+    vm = createTest(Empty, {
+      image: AXIOM
+    }, true);
+    expect(vm.$el.querySelector('.el-empty__image img')).to.exist;
+  });
+
+  it('should render imageSize props', async() => {
+    vm = createVue({
+      template: '<el-empty :image-size="imageSize"></el-empty>',
+      data() {
+        return {
+          imageSize: 500
+        };
+      }
+    }, true);
+    expect(vm.$el.querySelector('.el-empty__image').getAttribute('style')).to.contain('width: 500px');
+    vm.imageSize = 200;
+    await waitImmediate();
+    expect(vm.$el.querySelector('.el-empty__image').getAttribute('style')).to.contain('width: 200px');
+  });
+
+  it('should render description props', () => {
+    vm = createTest(Empty, {
+      description: AXIOM
+    }, true);
+    expect(vm.$el.querySelector('.el-empty__description').innerText).to.equal(AXIOM);
+  });
+
+  it('should render image slots', () => {
+    vm = createVue({
+      template: '<el-empty><template slot="image">{{AXIOM}}</template></el-empty>',
+      data() {
+        return {
+          AXIOM
+        };
+      }
+    }, true);
+    expect(vm.$el.querySelector('.el-empty__image').innerText).to.equal(AXIOM);
+  });
+
+  it('should render description slots', () => {
+    vm = createVue({
+      template: '<el-empty><template slot="description">{{AXIOM}}</template></el-empty>',
+      data() {
+        return {
+          AXIOM
+        };
+      }
+    }, true);
+    expect(vm.$el.querySelector('.el-empty__description').innerText).to.equal(AXIOM);
+  });
+
+  it('should render default slots', () => {
+    vm = createVue({
+      template: '<el-empty>{{AXIOM}}</el-empty>',
+      data() {
+        return {
+          AXIOM
+        };
+      }
+    }, true);
+    expect(vm.$el.querySelector('.el-empty__bottom').innerText).to.equal(AXIOM);
+  });
+});

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

@@ -82,6 +82,7 @@ import { ElDrawer } from './drawer'
 import { ElPopconfirm } from './popconfirm'
 import { ElSkeleton } from './skeleton'
 import { ElSkeletonItem } from './skeleton-item'
+import { ElEmpty } from './empty'
 
 export interface InstallationOptions {
   locale: any,
@@ -352,3 +353,6 @@ export class Skeleton extends ElSkeleton {}
 
 /** Skeleton Item Component */
 export class SkeletonItem extends ElSkeletonItem {}
+
+/** Empty Component */
+export class Empty extends ElEmpty {}

+ 31 - 0
types/empty.d.ts

@@ -0,0 +1,31 @@
+import { ElementUIComponent } from './component'
+import { VNode } from 'vue'
+
+interface ELEmptySlots {
+  /* default slot:  Custom bottom content */
+  default: VNode[]
+
+  /* image slot: Custom image */
+  image: VNode[]
+
+  /* description slot: Custom description */
+  description: VNode[]
+
+
+  [key: string]: VNode[]
+}
+
+/** Placeholder hints for empty states. */
+export declare class ElEmpty extends ElementUIComponent {
+  /* image URL */
+  image: string
+  	
+  /* image size (width) */
+  imageSize: number
+  
+  /* description */
+  description: string
+
+  $slots: ELEmptySlots
+
+}