diff --git a/mock/module/user.ts b/mock/module/user.ts
index ac49f17..94344f8 100644
--- a/mock/module/user.ts
+++ b/mock/module/user.ts
@@ -60,7 +60,7 @@ const userRoutes = [
         meta: {
           title: '多级菜单1',
           requiresAuth: true,
-          icon: 'icon-park-outline:alarm',
+          icon: 'icon-park-outline:list',
         },
       },
       {
@@ -69,7 +69,7 @@ const userRoutes = [
         meta: {
           title: '多级菜单2',
           requiresAuth: true,
-          icon: 'icon-park-outline:pic',
+          icon: 'icon-park-outline:list',
         },
         children: [
           {
@@ -78,7 +78,7 @@ const userRoutes = [
             meta: {
               title: '多级菜单2的详情页',
               requiresAuth: true,
-              icon: 'icon-park-outline:tool',
+              icon: 'icon-park-outline:list',
               hide: true,
               activeMenu: '/test/test2',
             },
@@ -91,7 +91,7 @@ const userRoutes = [
         meta: {
           title: '多级菜单3',
           requiresAuth: true,
-          icon: 'icon-park-outline:tool',
+          icon: 'icon-park-outline:list',
         },
         children: [
           {
@@ -100,7 +100,7 @@ const userRoutes = [
             meta: {
               title: '多级菜单3-1',
               requiresAuth: true,
-              icon: 'icon-park-outline:tool',
+              icon: 'icon-park-outline:list',
             },
           },
         ],
diff --git a/src/hook/index.ts b/src/hook/index.ts
index 8a6054a..73ca960 100644
--- a/src/hook/index.ts
+++ b/src/hook/index.ts
@@ -1,3 +1,4 @@
 export * from './useAppRouter';
 export * from './useBoolean';
 export * from './useLoading';
+export * from './useEcharts';
diff --git a/src/hook/useEcharts.ts b/src/hook/useEcharts.ts
new file mode 100644
index 0000000..7a44a85
--- /dev/null
+++ b/src/hook/useEcharts.ts
@@ -0,0 +1,87 @@
+import * as echarts from 'echarts/core';
+import { nextTick, ref, onUnmounted, onMounted } from 'vue';
+import type { Ref } from 'vue';
+
+import { BarChart, LineChart } from 'echarts/charts';
+// 系列类型的定义后缀都为 SeriesOption
+import type { BarSeriesOption, LineSeriesOption } from 'echarts/charts';
+
+import {
+  TitleComponent,
+  TooltipComponent,
+  GridComponent,
+  DatasetComponent, // 数据集组件
+  TransformComponent, // 内置数据转换器组件 (filter, sort)
+} from 'echarts/components';
+// 组件类型的定义后缀都为 ComponentOption
+import type {
+  TitleComponentOption,
+  TooltipComponentOption,
+  GridComponentOption,
+  DatasetComponentOption,
+} from 'echarts/components';
+
+import { LabelLayout, UniversalTransition } from 'echarts/features';
+import { CanvasRenderer } from 'echarts/renderers';
+
+// 通过 ComposeOption 来组合出一个只有必须组件和图表的 Option 类型
+export type ECOption = echarts.ComposeOption<
+  | BarSeriesOption
+  | LineSeriesOption
+  | TitleComponentOption
+  | TooltipComponentOption
+  | GridComponentOption
+  | DatasetComponentOption
+>;
+
+// 注册必须的组件
+echarts.use([
+  TitleComponent,
+  TooltipComponent,
+  GridComponent,
+  DatasetComponent,
+  TransformComponent,
+  BarChart,
+  LineChart,
+  LabelLayout,
+  UniversalTransition,
+  CanvasRenderer,
+]);
+
+export function useEcharts(options: Ref<ECOption>) {
+  const domRef = ref<HTMLElement>();
+
+  let chart: echarts.ECharts | null = null;
+
+  async function render() {
+    if (domRef.value) {
+      chart = echarts.init(domRef.value);
+      update(options.value);
+    }
+  }
+  function isRendered() {
+    return Boolean(domRef.value && chart);
+  }
+  function destroy() {
+    chart?.dispose();
+    chart = null;
+  }
+
+  function update(updateOptions: ECOption) {
+    if (isRendered()) {
+      chart!.setOption({ ...updateOptions, backgroundColor: 'transparent' });
+    }
+  }
+
+  onMounted(async () => {
+    await nextTick();
+    render();
+  });
+  onUnmounted(() => {
+    destroy();
+  });
+
+  return {
+    domRef,
+  };
+}
diff --git a/src/views/plugin/charts/echarts/index.vue b/src/views/plugin/charts/echarts/index.vue
index a9c5d58..1c8ccec 100644
--- a/src/views/plugin/charts/echarts/index.vue
+++ b/src/views/plugin/charts/echarts/index.vue
@@ -1,7 +1,38 @@
 <template>
-  <div>echarts</div>
+  <n-space :vertical="true" :size="16">
+    <n-card>
+      <div ref="pieRef" class="h-400px"></div>
+    </n-card>
+    <n-card>
+      <div ref="lineRef" class="h-400px"></div>
+    </n-card>
+  </n-space>
 </template>
 
-<script setup lang="ts"></script>
+<script setup lang="ts">
+import { ref } from 'vue';
+import type { Ref } from 'vue';
+import { type ECOption, useEcharts } from '@/hook';
+
+const pieOptions = ref<ECOption>({
+  title: {
+    text: 'ECharts 入门示例2',
+  },
+  tooltip: {},
+  xAxis: {
+    data: ['衬衫', '羊毛衫', '雪纺衫', '裤子', '高跟鞋', '袜子'],
+  },
+  yAxis: {},
+  series: [
+    {
+      name: '销量',
+      type: 'bar',
+      data: [5, 20, 36, 10, 10, 20],
+    },
+  ],
+}) as Ref<ECOption>;
+const { domRef: pieRef } = useEcharts(pieOptions);
+const { domRef: lineRef } = useEcharts(pieOptions);
+</script>
 
 <style scoped></style>