diff --git a/packages/components/src/components/empty/Empty.story.vue b/packages/components/src/components/empty/Empty.story.vue
new file mode 100644
index 00000000..a9e2c8a7
--- /dev/null
+++ b/packages/components/src/components/empty/Empty.story.vue
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+ 清空搜索
+ 新建文章
+
+
+
+
+
+
diff --git a/packages/components/src/components/empty/Empty.svg b/packages/components/src/components/empty/Empty.svg
new file mode 100644
index 00000000..f52a84fa
--- /dev/null
+++ b/packages/components/src/components/empty/Empty.svg
@@ -0,0 +1,90 @@
+
diff --git a/packages/components/src/components/empty/Empty.vue b/packages/components/src/components/empty/Empty.vue
new file mode 100644
index 00000000..c3243932
--- /dev/null
+++ b/packages/components/src/components/empty/Empty.vue
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+
{{ title }}
+
+
+ {{ message }}
+
+
+
+
+
+
+
+
diff --git a/packages/components/src/components/empty/__tests__/Empty.spec.ts b/packages/components/src/components/empty/__tests__/Empty.spec.ts
new file mode 100644
index 00000000..4b237e63
--- /dev/null
+++ b/packages/components/src/components/empty/__tests__/Empty.spec.ts
@@ -0,0 +1,95 @@
+import { describe, expect, it } from "vitest";
+import { mount } from "@vue/test-utils";
+import { VEmpty } from "../index";
+import { VButton } from "../../button";
+import { h } from "vue";
+
+describe("Empty", () => {
+ it("should render", () => {
+ expect(mount(VEmpty)).toBeDefined();
+ });
+
+ it("should match snapshot", () => {
+ expect(
+ mount(VEmpty, {
+ props: { title: "Not found", message: "No posts found" },
+ slots: {
+ actions: h(VButton, { type: "primary" }, "New Post"),
+ },
+ }).html()
+ ).toMatchSnapshot();
+ });
+
+ it("should work with title prop", () => {
+ const wrapper = mount(VEmpty, { props: { title: "Not found" } });
+ expect(wrapper.find(".empty-title").text()).toEqual("Not found");
+ });
+
+ it("should work with message prop", () => {
+ const wrapper = mount(VEmpty, { props: { message: "No posts found" } });
+ expect(wrapper.find(".empty-message").text()).toEqual("No posts found");
+ });
+
+ it("should work with message slot", () => {
+ const wrapper = mount(VEmpty, {
+ props: { message: "empty" },
+ slots: { message: h("span", h("storage", "No posts found")) },
+ });
+
+ expect(wrapper.find(".empty-message")).not.toEqual("Empty");
+ expect(wrapper.find(".empty-message > span > storage").text()).toEqual(
+ "No posts found"
+ );
+ });
+
+ it("should work with actions slot", () => {
+ const wrapper = mount(VEmpty, {
+ slots: {
+ actions: h(VButton, { type: "primary" }, "New Post"),
+ },
+ });
+
+ expect(wrapper.findComponent(VButton)).toBeDefined();
+ expect(wrapper.findComponent(VButton).find(".btn-content").text()).toEqual(
+ "New Post"
+ );
+ });
+
+ it("should work with image prop", async () => {
+ const wrapper = mount({
+ data() {
+ return {
+ image: "",
+ };
+ },
+ render() {
+ return h(VEmpty, {
+ image: this.image,
+ });
+ },
+ });
+
+ expect(wrapper.find(".empty-image > img").attributes().src).toEqual(
+ "/src/components/empty/Empty.svg"
+ );
+
+ await wrapper.setData({ image: "./empty.png" });
+
+ expect(wrapper.find(".empty-image > img").attributes().src).toEqual(
+ "./empty.png"
+ );
+ });
+
+ it("should work with image slot", () => {
+ const wrapper = mount(VEmpty, {
+ slots: { image: h("img", { src: "./empty", alt: "Empty Status" }) },
+ });
+
+ const attributes = wrapper.find(".empty-image > img").attributes();
+
+ expect(attributes.src).not.toEqual("/src/components/empty/Empty.svg");
+
+ expect(attributes.src).toEqual("./empty");
+ expect(attributes.alt).toEqual("Empty Status")
+ });
+});
diff --git a/packages/components/src/components/empty/__tests__/__snapshots__/Empty.spec.ts.snap b/packages/components/src/components/empty/__tests__/__snapshots__/Empty.spec.ts.snap
new file mode 100644
index 00000000..47d580dd
--- /dev/null
+++ b/packages/components/src/components/empty/__tests__/__snapshots__/Empty.spec.ts.snap
@@ -0,0 +1,12 @@
+// Vitest Snapshot v1
+
+exports[`Empty > should match snapshot 1`] = `
+"
+
+
Not found
+
No posts found
+
+
"
+`;
diff --git a/packages/components/src/components/empty/index.ts b/packages/components/src/components/empty/index.ts
new file mode 100644
index 00000000..28204c85
--- /dev/null
+++ b/packages/components/src/components/empty/index.ts
@@ -0,0 +1 @@
+export { default as VEmpty } from "./Empty.vue";