Repository: IduxFE/components (ts)

Issue:
[comp: Result] add component Result
- [x] I have searched the [issues](https://github.com/IduxFE/components/issues) of this repository and believe that this is not a duplicate.

用于反馈一系列操作任务的处理结果。

### What problem does this feature solve?
当有重要操作需告知用户处理结果，且反馈内容较为复杂时使用。

- 可以传入一个 `v-slot: extra` 来显示操作区；
- 可以传入一个 url 来自定义显示的图标，也可以传入一个 `v-slot: icon` 来显示图标；
- 可以传入 `status` 来控制按钮的颜色以及显示的图标；
- 可以传入 `title` 和 `subtitle` 来显示对应的文字，同时也支持 `slot` 的形式，优先级高于 `prop` ；
- 当需要显示复杂的说明时，可以传入一个 slot 来显示。

### What does the proposed API look like?
#### `ix-result`
##### Props
| 名称 | 说明 | 类型 | 默认值 | 全局配置 | 备注 |
| --- | ------- | ----- | -------  | --------- | ------ |
| `icon` | 自定义图标 | `string \| v-slot: icon` | - | - | 传入一个 name，表示 `ix-icon` 的图标，用来替换默认的图标；也可以使用 `slot` 的形式传入，优先级高于 prop |
| `status` | 结果的状态，决定图标和颜色 | `ResultStatus` | - | ✅ | - | 
| `subtitle` | subtitle 文字 | `string \| v-slot: subtitle` | - | - | `slot` 形式优先级高于 `prop` |
| `title` | `title` 文字 | `string \| v-slot: title` | - | - | `slot` 形式优先级高于 `prop` |
```typescript
type ResultStatus = 'success' | 'error' | 'info' | 'warning' | '404' | '403' | '500'
```
##### Slots
| 名称 | 说明 | 参数类型 | 备注 |
| ----  | ----- | ---------- | ----- |
| `default` | 复杂的情况说明，用于显示更多的信息 | - | - |
| `extra` | 操作区 | - | - |

Pull request description:
feat(comp: result): add result component
fix #112

## PR Checklist

Please check if your PR fulfills the following requirements:

- [x] The commit message follows [our guidelines](https://github.com/IduxFE/components/blob/main/docs/contributing.zh.md#commit)
- [x] Tests for the changes have been added/updated or not needed
- [x] Docs and demo have been added/updated or not needed

## PR Type

What kind of change does this PR introduce?

- [ ] Bugfix
- [x] Feature
- [ ] Component style update
- [ ] Code style update (formatting, local variables)
- [ ] Refactoring (no functional changes, no api changes)
- [ ] Build related changes
- [ ] CI related changes
- [ ] Documentation content changes
- [ ] Application (the showcase website) / infrastructure changes
- [ ] Other... Please describe:

## What is the current behavior?
<!-- Please describe the current behavior that you are modifying, or link to a relevant issue. -->

## What is the new behavior?

## Other information


Patch:
diff --git a/packages/components/badge/demo/Basic.vue b/packages/components/badge/demo/Basic.vue
index 7b2b3432..1f0796ff 100644
--- a/packages/components/badge/demo/Basic.vue
+++ b/packages/components/badge/demo/Basic.vue
@@ -12,7 +12,7 @@
       <a href="#"></a>
     </ix-badge>
 
-    <ix-badge count="1000" overflowCount="999">
+    <ix-badge count="1000" :overflowCount="999">
       <a href="#"></a>
     </ix-badge>
 
diff --git a/packages/components/badge/docs/index.zh.md b/packages/components/badge/docs/index.zh.md
index b25c9daa..5e4e1b95 100644
--- a/packages/components/badge/docs/index.zh.md
+++ b/packages/components/badge/docs/index.zh.md
@@ -26,7 +26,7 @@ cover:
 | --- | --- | --- | --- | --- | --- |
 | `dot` | 圆点徽标 | `boolean` | `false`  | ✅ | - |
 | `count` | 数值徽标的值 | `number\|string` | `0` | - | - |
-| `overflowCount` | 最大数值徽标的值 | `number\|string` | `99` | ✅ | 超出以`${overflowCount}+`显示 |
+| `overflowCount` | 最大数值徽标的值 | `number` | `99` | ✅ | 超出以`${overflowCount}+`显示 |
 | `showZero`| 数值为 0 时是否显示 | `boolean` | `false` | ✅ | - |
 | `color` | 徽标的颜色 | `string` | - | - | 数值徽标和圆点徽标为设置背景色，组件徽标为字体颜色 |
 
diff --git a/packages/components/badge/src/Badge.vue b/packages/components/badge/src/Badge.vue
index 384ad044..4d769db4 100644
--- a/packages/components/badge/src/Badge.vue
+++ b/packages/components/badge/src/Badge.vue
@@ -12,7 +12,7 @@ import type { ComputedRef } from 'vue'
 import type { BadgeProps } from './types'
 
 import { computed, defineComponent } from 'vue'
-import { hasSlot, isNumeric, PropTypes } from '@idux/cdk/utils'
+import { hasSlot, isNumeric, PropTypes, toNumber } from '@idux/cdk/utils'
 import { useGlobalConfig } from '@idux/components/core/config'
 
 export default defineComponent({
@@ -20,7 +20,7 @@ export default defineComponent({
   props: {
     count: PropTypes.oneOfType([Number, String]).def(0),
     showZero: PropTypes.bool,
-    overflowCount: PropTypes.oneOfType([Number, String]),
+    overflowCount: PropTypes.number,
     dot: PropTypes.bool,
     color: PropTypes.string,
   },
@@ -59,8 +59,10 @@ const useCountValue = (
       if (!showZero.value && +props.count === 0) {
         return false
       }
-      if (isNumeric(props.count) && isNumeric(overflowCount.value)) {
-        return props.count > overflowCount.value ? `${overflowCount.value}+` : `${props.count}`
+      if (isNumeric(props.count)) {
+        return props.count > toNumber(overflowCount.value, Number.MAX_VALUE)
+          ? `${overflowCount.value}+`
+          : `${props.count}`
       }
       return props.count
     }
diff --git a/packages/components/components.less b/packages/components/components.less
index c1e5d00f..645ce7cf 100644
--- a/packages/components/components.less
+++ b/packages/components/components.less
@@ -6,3 +6,4 @@
 @import './spin/style/index.less';
 @import './space/style/index.less';
 @import './empty/style/index.less';
+@import './result/style/index.less';
diff --git a/packages/components/core/config/defaultConfig.ts b/packages/components/core/config/defaultConfig.ts
index c017b6aa..d8d18b59 100644
--- a/packages/components/core/config/defaultConfig.ts
+++ b/packages/components/core/config/defaultConfig.ts
@@ -7,6 +7,7 @@ import type {
   ImageConfig,
   SpinConfig,
   SpaceConfig,
+  ResultConfig,
 } from './types'
 
 import { shallowReactive } from 'vue'
@@ -40,6 +41,8 @@ const spin = shallowReactive<SpinConfig>({
 
 const space = shallowReactive<SpaceConfig>({ size: 'small' })
 
+const result = shallowReactive<ResultConfig>({ status: 'info' })
+
 export const defaultConfig: GlobalConfig = {
   button,
   icon,
@@ -48,4 +51,5 @@ export const defaultConfig: GlobalConfig = {
   image,
   spin,
   space,
+  result,
 }
diff --git a/packages/components/core/config/types.ts b/packages/components/core/config/types.ts
index 914c9bf0..71e877ac 100644
--- a/packages/components/core/config/types.ts
+++ b/packages/components/core/config/types.ts
@@ -8,6 +8,7 @@ export interface GlobalConfig {
   image: ImageConfig
   spin: SpinConfig
   space: SpaceConfig
+  result: ResultConfig
 }
 
 export type ButtonMode = 'primary' | 'default' | 'dashed' | 'text' | 'link'
@@ -55,3 +56,8 @@ export type SpaceSize = 'small' | 'medium' | 'large' | number
 export interface SpaceConfig {
   size: SpaceSize
 }
+
+export type ResultStatus = 'success' | 'error' | 'info' | 'warning'
+export interface ResultConfig {
+  status: ResultStatus
+}
diff --git a/packages/components/icon/src/Icon.vue b/packages/components/icon/src/Icon.vue
index 4787c79c..c788ff27 100644
--- a/packages/components/icon/src/Icon.vue
+++ b/packages/components/icon/src/Icon.vue
@@ -61,7 +61,7 @@ async function appendChild(props: IconProps, iconConfig: IconConfig, root: Ref<H
       : await loadSVGElement(name, iconConfig.loadIconDynamically)
     if (svgElement) {
       handleRotate(svgElement, rotate)
-      root.value.appendChild(svgElement)
+      root.value?.appendChild(svgElement)
     }
   }
 }
diff --git a/packages/components/icon/src/icons.ts b/packages/components/icon/src/icons.ts
index 157b8b63..e81b23a4 100644
--- a/packages/components/icon/src/icons.ts
+++ b/packages/components/icon/src/icons.ts
@@ -10,6 +10,10 @@ import { RotateRight } from '../definitions/rotateRight'
 import { ZoomIn } from '../definitions/zoomIn'
 import { ZoomOut } from '../definitions/zoomOut'
 import { Close } from '../definitions/close'
+import { CheckCircle } from '../definitions/checkCircle'
+import { CloseCircle } from '../definitions/closeCircle'
+import { InfoCircle } from '../definitions/infoCircle'
+import { ExclamationCircle } from '../definitions/exclamationCircle'
 
 export const innerStaticIcons: IconDefinition[] = [
   Up,
@@ -23,4 +27,8 @@ export const innerStaticIcons: IconDefinition[] = [
   ZoomIn,
   ZoomOut,
   Close,
+  CheckCircle,
+  CloseCircle,
+  InfoCircle,
+  ExclamationCircle,
 ]
diff --git a/packages/components/index.ts b/packages/components/index.ts
index d1607d25..e46a8ad0 100644
--- a/packages/components/index.ts
+++ b/packages/components/index.ts
@@ -7,8 +7,9 @@ import { IxImage } from './image'
 import { IxSpin } from './spin'
 import { IxSpace } from './space'
 import { IxEmpty } from './empty'
+import { IxResult } from './result'
 
-const components = [IxButton, IxButtonGroup, IxIcon, IxBadge, IxDivider, IxImage, IxSpin, IxSpace, IxEmpty]
+const components = [IxButton, IxButtonGroup, IxIcon, IxBadge, IxDivider, IxImage, IxSpin, IxSpace, IxEmpty, IxResult]
 
 const install = (app: App): void => {
   components.forEach(component => {
@@ -33,3 +34,4 @@ export { IxImage }
 export { IxSpin }
 export { IxSpace }
 export { IxEmpty }
+export { IxResult }
diff --git a/packages/components/result/demo/error.md b/packages/components/result/demo/error.md
new file mode 100644
index 00000000..1d3c9e26
--- /dev/null
+++ b/packages/components/result/demo/error.md
@@ -0,0 +1,39 @@
+---
+order: 7
+title:
+  zh: 错误
+  en: Error
+---
+
+## zh
+
+复杂的错误反馈。
+
+## en
+
+Complex error feedback.
+
+## demo
+
+```html
+<template>
+  <ix-result
+    status="error"
+    title="Submission Failed"
+    subtitle="Please check and modify the following information before resubmitting."
+  >
+    <template v-slot:extra>
+      <ix-button mode="primary">Go console</ix-button>
+      <ix-button>Bug again</ix-button>
+    </template>
+    <!-- TODO 
+      use Typography
+     -->
+    The content you submitted has the following error:
+    <ul>
+      <li>Your account has been frozen. </li>
+      <li>Your account is not yet eligible to apply. </li>
+    </ul>
+  </ix-result>
+</template>
+```
diff --git a/packages/components/result/demo/info.md b/packages/components/result/demo/info.md
new file mode 100644
index 00000000..ddcdbafd
--- /dev/null
+++ b/packages/components/result/demo/info.md
@@ -0,0 +1,26 @@
+---
+order: 1
+title:
+  zh: 信息
+  en: Info
+---
+
+## zh
+
+展示处理的结果。
+
+## en
+
+Show the results of processing.
+
+## demo
+
+```html
+<template>
+  <ix-result title="Your operation has been executed">
+    <template v-slot:extra>
+      <ix-button mode="primary">Go console</ix-button>
+    </template>
+  </ix-result>
+</template>
+```
diff --git a/packages/components/result/demo/success.md b/packages/components/result/demo/success.md
new file mode 100644
index 00000000..9e2ec01d
--- /dev/null
+++ b/packages/components/result/demo/success.md
@@ -0,0 +1,27 @@
+---
+order: 0
+title:
+  zh: 成功
+  en: Success
+---
+
+## zh
+
+成功的结果。
+
+## en
+
+The result of success.
+
+## demo
+
+```html
+<template>
+  <ix-result status="success" title="Successfully watch the website." subtitle="Welcome again">
+    <template v-slot:extra>
+      <ix-button mode="primary">Go console</ix-button>
+      <ix-button>Buy Again</ix-button>
+    </template>
+  </ix-result>
+</template>
+```
diff --git a/packages/components/result/demo/warning.md b/packages/components/result/demo/warning.md
new file mode 100644
index 00000000..7af91a33
--- /dev/null
+++ b/packages/components/result/demo/warning.md
@@ -0,0 +1,26 @@
+---
+order: 2
+title:
+  zh: 警告
+  en: warning
+---
+
+## zh
+
+警告类型的结果。
+
+## en
+
+The result of the warning type.
+
+## demo
+
+```html
+<template>
+  <ix-result status="warning" title="There are some problems with your operation.">
+    <template v-slot:extra>
+      <ix-button mode="primary">Go console</ix-button>
+    </template>
+  </ix-result>
+</template>
+```
diff --git a/packages/components/result/docs/index.zh.md b/packages/components/result/docs/index.zh.md
new file mode 100644
index 00000000..1ecf4125
--- /dev/null
+++ b/packages/components/result/docs/index.zh.md
@@ -0,0 +1,61 @@
+---
+category: components
+type: 反馈
+title: Result
+subtitle: 结果
+cover:
+---
+
+用于反馈一系列操作任务的处理结果。
+
+## 何时使用
+
+当有重要操作需告知用户处理结果，且反馈内容较为复杂时使用。
+
+## API
+
+### `ix-result`
+
+#### Props
+
+| 名称 | 说明 | 类型  | 默认值 | 全局配置 | 备注 |
+| --- | --- | --- | --- | --- | --- |
+| `icon` | 自定义图标 | `string \| v-slot: icon` | - | - | - |
+| `status` | 当前结果的状态 | `ResultStatus` | - | ✅ | 结果的状态，决定图标和颜色 |
+| `subtitle` | subtitle 文字 | `string \| v-slot: subtitle` | - | - | `slot`形式优先级高于`prop` |
+| `title` | title文字 | `string \| v-slot: title` | - | - | `slot`形式优先级高于`prop` |
+
+```typescript
+type ResultStatus = 'success' | 'error' | 'info' | 'warning'
+```
+
+#### Slots
+
+| 名称 | 说明 | 参数类型 | 备注 |
+| --- | --- | --- | --- |
+| `default` | 复杂的情况说明，用于显示更多的信息 | - | - |
+| `extra` | 操作区 | - | - |
+
+### 主题变量
+
+| 变量名 | default 主题| 说明 |
+| --- | --- | --- |
+| @result-padding | @spacing-gutter *6 @spacing-gutter* 4 | - |
+| @result-text-align | `center` | - |
+| @result-icon-margin | @margin-xl | - |
+| @result-icon-size | @font-size-lg * 4 | - |
+| @result-title-font-size | @font-size-xl | - |
+| @result-title-color | @black-d20 | - |
+| @result-title-line-height | 1.8 | - |
+| @result-subtitle-font-size | @font-size-sm | - |
+| @result-subtitle-color | @black-l40 | - |
+| @result-subtitle-line-height | 1.6 | - |
+| @result-extra-margin | @margin-xl | - |
+| @result-extra-children-margin | @margin-sm | - |
+| @result-content-margin | @margin-xl | - |
+| @result-content-padding | @padding-xl @spacing-gutter * 5 | - |
+| @result-content-background | @grey-l50 | - |
+| @result-success-color | @green | - |
+| @result-warning-color | @yellow | - |
+| @result-error-color | @red | - |
+| @result-info-color | @blue | - |
diff --git a/packages/components/result/index.ts b/packages/components/result/index.ts
new file mode 100644
index 00000000..9b4d8713
--- /dev/null
+++ b/packages/components/result/index.ts
@@ -0,0 +1,7 @@
+import { installComponent } from '@idux/components/core/utils'
+import IxResult from './src/Result.vue'
+
+IxResult.install = installComponent(IxResult)
+
+export { IxResult }
+export * from './src/types'
diff --git a/packages/components/result/src/Result.vue b/packages/components/result/src/Result.vue
new file mode 100644
index 00000000..c91b6700
--- /dev/null
+++ b/packages/components/result/src/Result.vue
@@ -0,0 +1,79 @@
+<template>
+  <div class="ix-result" :class="className">
+    <div class="ix-result-icon">
+      <slot name="icon">
+        <ix-icon :name="currentIcon"></ix-icon>
+      </slot>
+    </div>
+    <div v-if="hasTitle || !!title" class="ix-result-title">
+      <slot name="title">{{ title }}</slot>
+    </div>
+    <div v-if="hasSubtitle || !!subtitle" class="ix-result-subtitle">
+      <slot name="subtitle">{{ subtitle }}</slot>
+    </div>
+    <div v-if="hasExtra" class="ix-result-extra">
+      <slot name="extra" />
+    </div>
+    <div v-if="hasContent" class="ix-result-content">
+      <slot />
+    </div>
+  </div>
+</template>
+<script lang="ts">
+import type { SetupContext } from 'vue'
+import type { ResultConfig, ResultStatus } from '@idux/components/core/config'
+import type { ResultProps } from './types'
+
+import { computed, defineComponent, Slots } from 'vue'
+import { hasSlot, PropTypes } from '@idux/cdk/utils'
+import { useGlobalConfig } from '@idux/components/core/config'
+import { IxIcon } from '@idux/components/icon'
+
+const defaultIconMap = new Map<ResultStatus, string>([
+  ['success', 'check-circle'],
+  ['error', 'close-circle'],
+  ['info', 'info-circle'],
+  ['warning', 'exclamation-circle'],
+])
+
+export default defineComponent({
+  name: 'IxResult',
+  components: { IxIcon },
+  props: {
+    icon: PropTypes.string,
+    status: PropTypes.oneOf(['success', 'error', 'info', 'warning'] as const),
+    subtitle: PropTypes.string,
+    title: PropTypes.string,
+  },
+  setup(props: ResultProps, { slots }: SetupContext) {
+    const resultConfig = useGlobalConfig('result')
+    const className = useClassName(props, resultConfig)
+    const hasTitle = useSlot(slots, 'title')
+    const hasSubtitle = useSlot(slots, 'subtitle')
+    const hasExtra = useSlot(slots, 'extra')
+    const hasContent = useSlot(slots)
+    const currentIcon = useIcon(props, resultConfig)
+
+    return { className, hasTitle, hasSubtitle, hasExtra, hasContent, currentIcon }
+  },
+})
+
+function useClassName(props: ResultProps, config: ResultConfig) {
+  return computed(() => {
+    const status = props.status ?? config.status
+
+    return [`ix-result-${status}`]
+  })
+}
+
+function useSlot(slots: Slots, key = 'default') {
+  return computed(() => hasSlot(slots, key))
+}
+
+function useIcon(props: ResultProps, config: ResultConfig) {
+  return computed(() => {
+    const status = props.status ?? config.status
+    return (props.icon ?? defaultIconMap.get(status)) as string
+  })
+}
+</script>
diff --git a/packages/components/result/src/types.ts b/packages/components/result/src/types.ts
new file mode 100644
index 00000000..d5b77643
--- /dev/null
+++ b/packages/components/result/src/types.ts
@@ -0,0 +1,28 @@
+import type { DefineComponent } from 'vue'
+import type { ResultStatus } from '@idux/components/core/config'
+
+interface ResultOriginalProps {
+  /* the name of `ix-icon` to replace the default icon. */
+  icon?: string
+  /**
+   * the status of result.
+   * It has different color of text and icon.
+   */
+  status?: ResultStatus
+  /**
+   * the subtitle of result.
+   * you can use slot.
+   * slot has higher level than prop.
+   */
+  subtitle?: string
+  /**
+   * the title of result.
+   * you can use slot.
+   * slot has higher level than prop.
+   */
+  title?: string
+}
+
+export type ResultProps = Readonly<ResultOriginalProps>
+
+export type ResultComponent = InstanceType<DefineComponent<ResultProps>>
diff --git a/packages/components/result/style/index.less b/packages/components/result/style/index.less
new file mode 100644
index 00000000..e58285fc
--- /dev/null
+++ b/packages/components/result/style/index.less
@@ -0,0 +1,50 @@
+@import '../../style/default.less';
+@import "./mixins.less";
+
+@result-prefix: ~'@{idux-prefix}-result';
+
+.@{result-prefix} {
+  padding: @result-padding;
+  text-align: @result-text-align;
+
+  &-icon {
+    margin-bottom: @result-icon-margin;
+
+    & > .@{icon-prefix} {
+      font-size: @result-icon-size;
+    }
+  }
+
+  &-title {
+    font-size: @result-title-font-size;
+    color: @result-title-color;
+    line-height: @result-title-line-height;
+  }
+
+  &-subtitle {
+    font-size: @result-subtitle-font-size;
+    line-height: @result-subtitle-line-height;
+    color: @result-subtitle-color;
+  }
+
+  &-extra {
+    margin-top: @result-extra-margin;
+
+
+    & > * {
+      margin-right: @result-extra-children-margin;
+
+      &:last-child {
+        margin-right: 0;
+      }
+    }
+  }
+
+  &-content {
+    margin-top: @result-content-margin;
+    padding: @result-content-padding;
+    background-color: @result-content-background;
+  }
+}
+
+.result-status-traverse(@result-prefix);
diff --git a/packages/components/result/style/mixins.less b/packages/components/result/style/mixins.less
new file mode 100644
index 00000000..3bbeca80
--- /dev/null
+++ b/packages/components/result/style/mixins.less
@@ -0,0 +1,24 @@
+@import "../../style/default.less";
+
+/* stylelint-disable property-no-unknown */
+@result-status: {
+  success: @result-success-color;
+  warning: @result-warning-color;
+  error: @result-error-color;
+  info: @result-info-color;
+};
+/* stylelint-enable property-no-unknown */
+
+.result-status-traverse(@prefix) {
+  .@{prefix} {
+    each(@result-status, .(@value, @key) {
+      @result-status-item: ~'@{prefix}-@{key}';
+
+      &.@{result-status-item} {
+        .@{prefix}-icon {
+          color: @value;
+        }
+      }
+    })
+  }
+}
diff --git a/packages/components/style/default.less b/packages/components/style/default.less
index e146ad7a..a1394a92 100644
--- a/packages/components/style/default.less
+++ b/packages/components/style/default.less
@@ -91,3 +91,24 @@
 @empty-line-height-md: @line-height-base;
 @empty-font-size-md: @font-size-md;
 @empty-icon-font-size-md: 64px;
+
+/* space -------------------------- */
+@result-padding: @spacing-gutter * 6 @spacing-gutter * 4;
+@result-text-align: center;
+@result-icon-margin: @margin-xl;
+@result-icon-size: @font-size-lg * 4;
+@result-title-font-size: @font-size-xl;
+@result-title-color: @black-d20;
+@result-title-line-height: 1.8;
+@result-subtitle-font-size: @font-size-sm;
+@result-subtitle-color: @black-l40;
+@result-subtitle-line-height: 1.6;
+@result-extra-margin: @margin-xl;
+@result-extra-children-margin: @margin-sm;
+@result-content-margin: @margin-xl;
+@result-content-padding: @padding-xl @spacing-gutter * 5;
+@result-content-background: @grey-l50;
+@result-success-color: @success-color;
+@result-warning-color: @warning-color;
+@result-error-color: @error-color;
+@result-info-color: @info-color;


Test patch:
diff --git a/packages/components/badge/__tests__/__snapshots__/badge.spec.ts.snap b/packages/components/badge/__tests__/__snapshots__/badge.spec.ts.snap
index bbcf8087..d8826276 100644
--- a/packages/components/badge/__tests__/__snapshots__/badge.spec.ts.snap
+++ b/packages/components/badge/__tests__/__snapshots__/badge.spec.ts.snap
@@ -18,6 +18,10 @@ exports[`Badge.vue overflowCount work 1`] = `"<sup class=\\"ix-badge ix-badge-em
 
 exports[`Badge.vue overflowCount work 2`] = `"<sup class=\\"ix-badge ix-badge-empty ix-badge-count\\">999+</sup>"`;
 
+exports[`Badge.vue overflowCount work 3`] = `"<sup class=\\"ix-badge ix-badge-empty ix-badge-count\\">999+</sup>"`;
+
+exports[`Badge.vue overflowCount work 4`] = `"<sup class=\\"ix-badge ix-badge-empty ix-badge-count\\">1000</sup>"`;
+
 exports[`Badge.vue render work 1`] = `"<span class=\\"ix-badge-wrapper\\"><sup class=\\"ix-badge ix-badge-empty ix-badge-count ix-badge-hide-zero\\"><!--v-if--></sup></span>"`;
 
 exports[`Badge.vue showZero work 1`] = `"<sup class=\\"ix-badge ix-badge-empty ix-badge-count ix-badge-hide-zero\\"><!--v-if--></sup>"`;
diff --git a/packages/components/badge/__tests__/badge.spec.ts b/packages/components/badge/__tests__/badge.spec.ts
index 485c0bee..35d15313 100644
--- a/packages/components/badge/__tests__/badge.spec.ts
+++ b/packages/components/badge/__tests__/badge.spec.ts
@@ -57,6 +57,17 @@ describe('Badge.vue', () => {
     await wrapper.setProps({ count: '1000', overflowCount: 999 })
     expect(badge.text()).toBe('999+')
     expect(badge.html()).toMatchSnapshot()
+
+    const warn = jest.spyOn(console, 'warn').mockImplementation(() => {})
+
+    await wrapper.setProps({ count: '1000', overflowCount: '999' })
+    expect(badge.text()).toBe('999+')
+    expect(badge.html()).toMatchSnapshot()
+    await wrapper.setProps({ count: '1000', overflowCount: 'xxx' })
+    expect(badge.text()).toBe('1000')
+    expect(badge.html()).toMatchSnapshot()
+
+    expect(warn).toBeCalledTimes(2)
   })
 
   // 圆点徽标呈现
diff --git a/packages/components/result/__tests__/__snapshots__/result.spec.ts.snap b/packages/components/result/__tests__/__snapshots__/result.spec.ts.snap
new file mode 100644
index 00000000..f214c9c3
--- /dev/null
+++ b/packages/components/result/__tests__/__snapshots__/result.spec.ts.snap
@@ -0,0 +1,3 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Result.vue render work 1`] = `"<div class=\\"ix-result ix-result-info\\"><div class=\\"ix-result-icon\\"><i class=\\"ix-icon ix-icon-info-circle\\" role=\\"img\\" arialabel=\\"info-circle\\"></i></div><!--v-if--><!--v-if--><!--v-if--><!--v-if--></div>"`;
diff --git a/packages/components/result/__tests__/result.spec.ts b/packages/components/result/__tests__/result.spec.ts
new file mode 100644
index 00000000..6ad152c9
--- /dev/null
+++ b/packages/components/result/__tests__/result.spec.ts
@@ -0,0 +1,100 @@
+import { mount, MountingOptions, VueWrapper } from '@vue/test-utils'
+import { defineComponent, DefineComponent } from 'vue'
+import IxResult from '../src/Result.vue'
+import { ResultProps } from '../src/types'
+import { IxIcon } from '../../icon'
+
+describe('Result.vue', () => {
+  let ResultMount: (
+    options?: MountingOptions<Partial<ResultProps>>,
+  ) => VueWrapper<InstanceType<DefineComponent<ResultProps>>>
+
+  beforeEach(() => {
+    ResultMount = (options = {}) => {
+      return mount<ResultProps>(IxResult, {
+        ...options,
+      })
+    }
+  })
+
+  test('render work', () => {
+    const wrapper = ResultMount()
+    expect(wrapper.html()).toMatchSnapshot()
+  })
+
+  test('status work', async () => {
+    const wrapper = ResultMount()
+    expect(wrapper.findComponent(IxIcon).exists()).toBeTruthy()
+    expect(wrapper.findComponent(IxIcon).props().name).toEqual('info-circle')
+    expect(wrapper.classes()).toContain('ix-result-info')
+
+    await wrapper.setProps({ status: 'success' })
+    expect(wrapper.classes()).toContain('ix-result-success')
+    expect(wrapper.findComponent(IxIcon).props().name).toEqual('check-circle')
+
+    await wrapper.setProps({ status: 'warning' })
+    expect(wrapper.classes()).toContain('ix-result-warning')
+    expect(wrapper.findComponent(IxIcon).props().name).toEqual('exclamation-circle')
+
+    await wrapper.setProps({ status: 'error' })
+    expect(wrapper.classes()).toContain('ix-result-error')
+    expect(wrapper.findComponent(IxIcon).props().name).toEqual('close-circle')
+
+    await wrapper.setProps({ status: 'info' })
+    expect(wrapper.classes()).toContain('ix-result-info')
+    expect(wrapper.findComponent(IxIcon).props().name).toEqual('info-circle')
+  })
+
+  test('title work', async () => {
+    const wrapper = ResultMount()
+    expect(wrapper.find('.ix-result-title').exists()).toBeFalsy()
+
+    await wrapper.setProps({ title: 'Title' })
+    expect(wrapper.find('.ix-result-title').exists()).toBeTruthy()
+  })
+
+  test('subtitle work', async () => {
+    const wrapper = ResultMount()
+    expect(wrapper.find('.ix-result-subtitle').exists()).toBeFalsy()
+
+    await wrapper.setProps({ subtitle: 'Subtitle' })
+    expect(wrapper.find('.ix-result-subtitle').exists()).toBeTruthy()
+  })
+
+  test('icon work', () => {
+    const wrapper = ResultMount({ props: { icon: 'check-circle' } })
+    expect(wrapper.findComponent(IxIcon).props().name).toEqual('check-circle')
+  })
+
+  test('slot work', () => {
+    const title = 'This is a title.'
+    const subtitle = 'This is a subtitle.'
+    const extra = 'This is the extra.'
+    const TestComponent = defineComponent({
+      components: { IxResult },
+      template: `
+      <IxResult>
+        <template>
+          <ul>
+            <li>1</li>
+            <li>2</li>
+          </ul>
+        </template>
+        <template v-slot:title>${title}</template>
+        <template v-slot:subtitle>${subtitle}</template>
+        <template v-slot:extra>${extra}</template>
+      </IxResult>
+      `,
+    })
+    const wrapper = mount(TestComponent)
+    expect(wrapper.find('.ix-result-content').exists()).toBeTruthy()
+    expect(wrapper.find('.ix-result-title').exists()).toBeTruthy()
+    expect(wrapper.find('.ix-result-subtitle').exists()).toBeTruthy()
+    expect(wrapper.find('.ix-result-extra').exists()).toBeTruthy()
+
+    expect(wrapper.get('.ix-result-content').get('ul').html()).toEqual('<ul><li>1</li><li>2</li></ul>')
+    expect(wrapper.get('.ix-result-title').text()).toEqual(title)
+    expect(wrapper.get('.ix-result-subtitle').text()).toEqual(subtitle)
+    expect(wrapper.get('.ix-result-extra').text()).toEqual(extra)
+  })
+})