Parcourir la source

feat: 添加echarts

liaojiaxing il y a 10 mois
Parent
commit
714eb73a11

+ 1 - 0
package.json

@@ -14,6 +14,7 @@
     "@vueuse/core": "^10.10.1",
     "ant-design-vue": "4.x",
     "dayjs": "^1.11.11",
+    "echarts": "^5.5.0",
     "less": "^4.2.0",
     "less-loader": "^12.2.0",
     "pinia": "^2.1.7",

+ 20 - 0
pnpm-lock.yaml

@@ -20,6 +20,9 @@ dependencies:
   dayjs:
     specifier: ^1.11.11
     version: 1.11.11
+  echarts:
+    specifier: ^5.5.0
+    version: 5.5.0
   less:
     specifier: ^4.2.0
     version: 4.2.0
@@ -753,6 +756,13 @@ packages:
     resolution: {integrity: sha512-bvVTQe1lfaUr1oFzZX80ce9KLDlZ3iU+XGNE/bz9HnGdklTieqsbmsLHe+rT2XWqopvL0PckkYqN7ksmm5pe3w==}
     dev: false
 
+  /echarts@5.5.0:
+    resolution: {integrity: sha512-rNYnNCzqDAPCr4m/fqyUFv7fD9qIsd50S6GDFgO1DxZhncCsNsG7IfUlAlvZe5oSEQxtsjnHiUuppzccry93Xw==}
+    dependencies:
+      tslib: 2.3.0
+      zrender: 5.5.0
+    dev: false
+
   /entities@4.5.0:
     resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
     engines: {node: '>=0.12'}
@@ -1084,6 +1094,10 @@ packages:
     resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==}
     engines: {node: '>=4'}
 
+  /tslib@2.3.0:
+    resolution: {integrity: sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==}
+    dev: false
+
   /tslib@2.6.3:
     resolution: {integrity: sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==}
 
@@ -1219,3 +1233,9 @@ packages:
     dependencies:
       loose-envify: 1.4.0
     dev: false
+
+  /zrender@5.5.0:
+    resolution: {integrity: sha512-O3MilSi/9mwoovx77m6ROZM7sXShR/O/JIanvzTwjN3FORfLSr81PsUGd7jlaYOeds9d8tw82oP44+3YucVo+w==}
+    dependencies:
+      tslib: 2.3.0
+    dev: false

+ 4 - 0
src/components/Charts/Line/BasicLine/index.ts

@@ -0,0 +1,4 @@
+import BasicLine from './src/index.vue';
+export default BasicLine;
+
+export { defaultPropsValue, basicLineProps } from './src/props';

+ 54 - 0
src/components/Charts/Line/BasicLine/src/index.vue

@@ -0,0 +1,54 @@
+<template>
+  <div ref="chartRef" :style="style"></div>
+</template>
+
+<script setup lang="ts">
+import { ref, Ref, defineProps, watch, nextTick, computed } from "vue";
+import { useEcharts } from "@/hooks/useEcharts";
+import { basicLineProps } from "./props";
+
+const props = defineProps(basicLineProps);
+const chartRef: Ref<null | HTMLDivElement> = ref(null);
+const { setOptions } = useEcharts(chartRef as Ref<HTMLDivElement>);
+const style = computed(() => {
+  const { width, height } = props;
+  return {
+    width: `${width}px`,
+    height: `${height}px`,
+  };
+});
+
+watch(
+  () => props,
+  async () => {
+    await nextTick();
+    const {
+      title,
+      legend,
+      grid,
+      backgroundColor,
+      tooltip,
+      xAxis,
+      yAxis,
+      series,
+      dataset,
+    } = props;
+
+    setOptions({
+      title,
+      grid,
+      backgroundColor,
+      legend,
+      tooltip,
+      xAxis,
+      yAxis,
+      series,
+      dataset,
+    });
+  },
+  {
+    immediate: true
+  });
+</script>
+
+<style scoped></style>

+ 85 - 0
src/components/Charts/Line/BasicLine/src/props.ts

@@ -0,0 +1,85 @@
+import { type PropType } from 'vue';
+import { EChartsOption, color } from 'echarts';
+
+export const basicLineProps = {
+  width: {
+    type: Number as PropType<number>,
+    default: 400,
+  },
+  height: {
+    type: Number as PropType<number>,
+    default: 260,
+  },
+  // 标题
+  title: {
+    type: Object as PropType<EChartsOption['title']>,
+  },
+  // 图例
+  legend: {
+    type: Object as PropType<EChartsOption['legend']>
+  },
+  // 背景
+  backgroundColor: {
+    type: String as PropType<string>,
+  },
+  // 边框
+  grid: {
+    type: Object as PropType<EChartsOption['grid']>,
+  },
+  // 提示框
+  tooltip: {
+    type: Object as PropType<EChartsOption['tooltip']>,
+  },
+  // x轴数据
+  xAxis: {
+    type: Object as PropType<EChartsOption['xAxis']>,
+  },
+  // y轴数据
+  yAxis: {
+    type: Object as PropType<EChartsOption['yAxis']>,
+  },
+  // 折线
+  series: {
+    type: Array as PropType<EChartsOption['series']>,
+  },
+  // 数据集
+  dataset: {
+    type: Object as PropType<EChartsOption['dataset']>,
+  },
+};
+
+export const defaultPropsValue = {
+  width: 400,
+  height: 260,
+  legend: {
+  },
+  tooltip: {},
+  xAxis: {
+    type: 'category',
+    itemStyle: {
+      color: '#fff'
+    }
+  },
+  yAxis: {},
+  series: [
+    { type: 'line', itemStyle: { color: '#1890ff' }},
+    { type: 'line', itemStyle: { color: '#2fc25b' }}
+  ],
+  dataset: {
+    dimensions: ['month', 'city', 'city2'],
+    source: [
+      { month: '一月', city: Math.floor(Math.random() * 100), city2: Math.floor(Math.random() * 100) },
+      { month: '二月', city: Math.floor(Math.random() * 100), city2: Math.floor(Math.random() * 100) },
+      { month: '三月', city: Math.floor(Math.random() * 100), city2: Math.floor(Math.random() * 100) },
+      { month: '四月', city: Math.floor(Math.random() * 100), city2: Math.floor(Math.random() * 100) },
+      { month: '五月', city: Math.floor(Math.random() * 100), city2: Math.floor(Math.random() * 100) },
+      { month: '六月', city: Math.floor(Math.random() * 100), city2: Math.floor(Math.random() * 100) },
+      { month: '七月', city: Math.floor(Math.random() * 100), city2: Math.floor(Math.random() * 100) },
+      { month: '八月', city: Math.floor(Math.random() * 100), city2: Math.floor(Math.random() * 100) },
+      { month: '九月', city: Math.floor(Math.random() * 100), city2: Math.floor(Math.random() * 100) },
+      { month: '十月', city: Math.floor(Math.random() * 100), city2: Math.floor(Math.random() * 100) },
+      { month: '十一月', city: Math.floor(Math.random() * 100), city2: Math.floor(Math.random() * 100) },
+      { month: '十二月', city: Math.floor(Math.random() * 100), city2: Math.floor(Math.random() * 100) },
+    ],
+  }
+}

+ 1 - 0
src/components/index.ts

@@ -1,6 +1,7 @@
 // 导出components文件夹下所有组件
 const allComponents = {
   Title: () => import("@/components/Text/Title"),
+  BasicLine: () => import("@/components/Charts/Line/BasicLine"),
 };
 
 export default allComponents;

+ 2 - 2
src/config/compSetting.ts

@@ -62,7 +62,7 @@ export const compSetting: CompSetting = {
           children: [
             {
               name: '柱状图',
-              componetName: 'BaseBar',
+              componetName: 'BasicBar',
               icon: compIcon['icon-1']
             }
           ]
@@ -72,7 +72,7 @@ export const compSetting: CompSetting = {
           children: [
             {
               name: '折线图',
-              componetName: 'BaseLine',
+              componetName: 'BasicLine',
               icon: compIcon['icon-2']
             }
           ]

+ 102 - 0
src/hooks/useEcharts.ts

@@ -0,0 +1,102 @@
+import type { Ref } from "vue";
+import type { EChartsOption } from 'echarts';
+import echarts from "@/utils/echarts";
+// import * as echarts from 'echarts';
+import { unref, nextTick, watch, computed, ref } from "vue";
+import { tryOnUnmounted, useDebounceFn, useEventListener } from '@vueuse/core';
+
+export function useEcharts(
+  elRef: Ref<HTMLElement>,
+  theme: 'default' | 'dark' | 'light' = 'default',
+) {
+  let chartInstance: echarts.ECharts | null = null;
+  let resizeFn: Fn = resize;
+  const cacheOptions = ref({});
+
+  let removeResizeFn: Fn = () => {};
+
+  const getOptions = computed(() => {
+    return cacheOptions.value;
+  })
+
+  resizeFn = useDebounceFn(resize, 200);
+
+  function resize() {
+    chartInstance?.resize({
+      animation: {
+        duration: 300,
+        easing: 'quadraticIn',
+      },
+    });
+  }
+
+  function initCharts( t = theme) {
+    const el = unref(elRef);
+    if (!el || !unref(el)) {
+      return;
+    }
+
+    chartInstance = echarts.init(el, t);
+    const removeEvent = useEventListener(window, 'resize', resizeFn);
+
+    removeResizeFn = removeEvent;
+  }
+
+  function setOptions(options: EChartsOption, clear = true) {
+    cacheOptions.value = options;
+    return new Promise((resolve) => {
+      if (unref(elRef)?.offsetHeight === 0) {
+        setTimeout(() => {
+          setOptions(unref(getOptions));
+          resolve(null);
+        }, 30);
+      }
+      nextTick(() => {
+        setTimeout(() => {
+          if (!chartInstance) {
+            initCharts('default');
+
+            if (!chartInstance) return;
+          }
+          clear && chartInstance?.clear();
+
+          chartInstance?.setOption(unref(getOptions));
+          resolve(null);
+        }, 30);
+      });
+    });
+  }
+
+  watch(
+    () => theme,
+    (theme) => {
+      if (chartInstance) {
+        chartInstance.dispose();
+        initCharts(theme as 'default');
+        setOptions(cacheOptions.value);
+      }
+    },
+  );
+
+
+  tryOnUnmounted(() => {
+    if (!chartInstance) return;
+    removeResizeFn();
+    chartInstance.dispose();
+    chartInstance = null;
+  });
+
+  function getInstance(): echarts.ECharts | null {
+    if (!chartInstance) {
+      initCharts('default');
+    }
+    return chartInstance;
+  }
+
+  return {
+    setOptions,
+    resize,
+    echarts,
+    getInstance
+  }
+}

+ 62 - 0
src/utils/echarts.ts

@@ -0,0 +1,62 @@
+import * as echarts from 'echarts/core';
+
+import {
+  BarChart,
+  LineChart,
+  PieChart,
+  MapChart,
+  PictorialBarChart,
+  RadarChart,
+  ScatterChart,
+} from 'echarts/charts';
+
+import {
+  TitleComponent,
+  TooltipComponent,
+  GridComponent,
+  PolarComponent,
+  AriaComponent,
+  ParallelComponent,
+  LegendComponent,
+  RadarComponent,
+  ToolboxComponent,
+  DataZoomComponent,
+  VisualMapComponent,
+  TimelineComponent,
+  CalendarComponent,
+  GraphicComponent,
+  DatasetComponent,
+  // 内置数据转换器组件 (filter, sort)
+  TransformComponent
+} from 'echarts/components';
+
+import { CanvasRenderer } from 'echarts/renderers';
+
+echarts.use([
+  LegendComponent,
+  TitleComponent,
+  TooltipComponent,
+  GridComponent,
+  PolarComponent,
+  AriaComponent,
+  ParallelComponent,
+  BarChart,
+  LineChart,
+  PieChart,
+  MapChart,
+  RadarChart,
+  CanvasRenderer,
+  PictorialBarChart,
+  RadarComponent,
+  ToolboxComponent,
+  DataZoomComponent,
+  VisualMapComponent,
+  TimelineComponent,
+  CalendarComponent,
+  GraphicComponent,
+  ScatterChart,
+  DatasetComponent,
+  TransformComponent
+]);
+
+export default echarts;

+ 1 - 0
src/views/designer/component/ComponentWrapper.vue

@@ -194,6 +194,7 @@ const handleDragEnd = () => {
   height: 100%;
   left: 0;
   top: 0;
+  overflow: hidden;
 }
 .edit-box {
   position: absolute;

+ 0 - 1
src/views/designer/component/Stage.vue

@@ -165,7 +165,6 @@ const handleDrop = (e: DragEvent) => {
 
 // 点击画布,处理组件选中状态
 const handleCanvasClick = (e: MouseEvent) => {
-  console.log(e.target)
   if(
     !e?.target?.closest(".edit-box") 
     && !e?.target?.closest(".component-content")

+ 3 - 0
types/index.d.ts

@@ -0,0 +1,3 @@
+declare interface Fn<T = any, R = T> {
+  (...arg: T[]): R;
+}