前端低代码开发的那些事

483 阅读6分钟

前言

低代码平台(Low Code Development Platform,LCDP)是一款面向业务人员和IT人员双向驱动管理的能力平台,更是数字化转型中必不可少的数字技术工具平台。

低代码开发(Low-code Development)是一种快速应用程序开发的方法,通过使用可视化编程界面和拖放组件来创建网页和移动应用程序,而不需要编写大量代码。

低代码平台的价值在于:

  • 开发速度快,能够实现业务应用的快速交付。
  • 能够降低业务应用的开发成本,代码的编写更少、环境的设置和部署成本也更简单。
  • 能够实现企业应用过程中的二次开发,增加产品的可扩展性和灵活性。

主要功能区域

image.png

一个低代码平台主要包括以下区域:

  1. 物料区域(可选组件区域,包括各种图标,表格以及各种小物件)
  2. 图层区域,主要是给选择的物料一个可以缩略的区域
  3. 编辑区域,可以对选择的物料进行编辑

包含的主要技术有

  • 可视化开发技术:低代码平台前端交互以可视化编辑器为主,可对UI界面、交互事件、后端接口、数据库/Redis调用等进行配置和编辑。
  • 组件化定制化开发:将重复的公共的能力沉淀出来,封装为组件,让开发人员可以在低代码平台上直接使用。
  • 元素拖拽技术:屏蔽了具体的代码选型,内部编辑都用一种低代码语言,最后发布上线,可发布到各个端口。

EasyView 中拖拽功能实现

  1. 添加拖拽属性
  2. 拖拽参数传递、组件注册和安装
  3. 以柱状图组件为例,如何去定义和实现一个标准拖拽组件

添加拖拽属性

对于需要拖拽的每一项,添加 draggable 属性

image.png

在拖拉开始的时候(dragstart),首先判断这项是否可以拖拉,然后判断一下这个组件是否已经注册,没有注册的情况下,首先进行注册。

image.png 注册包括两方面内容,一个是组件本身,另外一个是组件相关配置项组件。

拖拽参数传递、组件注册和安装

之后通过事件中的dataTransfer属性里面胡setData方法,传递组件中的参数

image.png 一个典型组件的配置项包括以下内容

image.png

对于编辑区域,对于画布主体来说,首先定义好接受拖拽组件的相关事件,包括 drop,dragover和dragenter等

image.png

`

// * 拖拽到编辑区域里

export const dragHandle = async (e: DragEvent) => {

  e.preventDefault()
  try {
    loadingStart()
    // 获取拖拽数据

    const drayDataString = e!.dataTransfer!.getData(DragKeyEnum.DRAG_KEY)

    if (!drayDataString) {

      loadingFinish()

      return

    }
    // 修改状态

    chartEditStore.setEditCanvas(EditCanvasTypeEnum.IS_CREATE, false)

    const dropData: Exclude<ConfigType, ['image']> = JSONParse(drayDataString)

    if (dropData.disabled) return

    // 创建新图表组件

    let newComponent: CreateComponentType = await createComponent(dropData)

    if (dropData.redirectComponent) {

      dropData.dataset && (newComponent.option.dataset = dropData.dataset)

      newComponent.chartConfig.title = dropData.title

      newComponent.chartConfig.chartFrame = dropData.chartFrame

    }

    setComponentPosition(newComponent, e.offsetX - newComponent.attr.w / 2, e.offsetY - newComponent.attr.h / 2)

    chartEditStore.addComponentList(newComponent, false, true)

    chartEditStore.setTargetSelectChart(newComponent.id)

    loadingFinish()

  } catch (error) {

    loadingError()

    window['$message'].warning(`图表正在研发中, 敬请期待...`)

  }

}



// * 进入拖拽区域

export const dragoverHandle = (e: DragEvent) => {
  e.preventDefault()
  e.stopPropagation()
  if (e.dataTransfer) e.dataTransfer.dropEffect = 'copy'

}

以柱状图组件为例,如何去定义和实现一个标准拖拽组件

对于每一个自定义的拖拽组件,里面都包含以下的几个文件

image.png

  • config.ts 柱状图配置文件
  • config.vue 柱状图的配置页面文件
  • data.json 柱状图数据文件
  • index.ts 柱状图的配置文件
  • index.vue 柱状图的组件文件

config.ts 如下所示:

// echartOptionProfixHandle 全局主题颜色
import { echartOptionProfixHandle, PublicConfigClass } from '@/packages/public';
// Bar 的配置项
import { BarCommonConfig } from './index';
import { CreateComponentType } from '@/packages/index.d';
import cloneDeep from 'lodash/cloneDeep';
import dataJson from './data.json';
export const includes = ['legend', 'xAxis', 'yAxis', 'grid'];
export const seriesItem = {
  type: 'bar',
  barWidth: 15,
  label: {
    show: true,
    postion: 'top',
    color: '#fff',
    fontSize: 12,
  },
  itemStyle: {
    color: null,
    borderRadius: 2,
  },
};

export const option = {
  tooltip: {
    show: true,
    trigger: 'axis',
    axisPointer: {
      show: true,
      type: 'shadow',
    },
  },
  xAxis: {
    show: true,
    type: 'category',
  },
  yAxis: {
    show: true,
    type: 'value',
  },
  dataset: {
    ...dataJson,
  },
  series: [seriesItem, seriesItem],
};
export default class Config extends PublicConfigClass implements CreateComponentType {
  public key = BarCommonConfig.key;
  public chartConfig = cloneDeep(BarCommonConfig);
  public option = echartOptionProfixHandle(option, includes);
}

BarCommonConfig,即index.ts中 的内容如下

import { ConfigType, PackagesCategoryEnum, ChartFrameEnum } from '@/packages/index.d';
import { ChatCategoryEnum, ChatCategoryEnumName } from '../../index.d';
export const BarCommonConfig: ConfigType = {
  key: 'BarCommon',
  chartKey: 'VBarCommon',
  conKey: 'VCBarCommon',
  title: '普通柱状图',
  category: ChatCategoryEnum.BAR,
  categoryName: ChatCategoryEnumName.BAR,
  package: PackagesCategoryEnum.CHARTS,
  chartFrame: ChartFrameEnum.ECHARTS,
  image: 'bar_x.png',
};

对于每个配置页面,如下所示

<template>
  <!-- Echarts 全局设置 -->
  <global-setting :optionData="optionData"></global-setting>
  <CollapseItem v-for="(item, index) in seriesList" :key="index" :name="`柱状图-${index + 1}`" :expanded="true">
    <SettingItemBox name="图形">
      <SettingItem name="宽度">
        <n-input-number
          v-model:value="item.barWidth"
          :min="1"
          :max="100"
          size="small"
          placeholder="自动计算"
        ></n-input-number>
      </SettingItem>
      <SettingItem name="圆角">
        <n-input-number v-model:value="item.itemStyle.borderRadius" :min="0" size="small"></n-input-number>
      </SettingItem>
    </SettingItemBox>
    <setting-item-box name="标签">
      <setting-item>
        <n-space>
          <n-switch v-model:value="item.label.show" size="small" />
          <n-text>展示标签</n-text>
        </n-space>
      </setting-item>
      <setting-item name="大小">
        <n-input-number v-model:value="item.label.fontSize" size="small" :min="1"></n-input-number>
      </setting-item>
      <setting-item name="颜色">
        <n-color-picker size="small" :modes="['hex']" v-model:value="item.label.color"></n-color-picker>
      </setting-item>
      <setting-item name="位置">
        <n-select
          v-model:value="item.label.position"
          :options="[
            { label: 'top', value: 'top' },
            { label: 'left', value: 'left' },
            { label: 'right', value: 'right' },
            { label: 'bottom', value: 'bottom' }
          ]"
        />
      </setting-item>
    </setting-item-box>
  </CollapseItem>
</template>

<script setup lang="ts">
import { PropType, computed } from 'vue'
import { GlobalSetting, CollapseItem, SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting'
import { GlobalThemeJsonType } from '@/settings/chartThemes/index'

const props = defineProps({
  optionData: {
    type: Object as PropType<GlobalThemeJsonType>,
    required: true
  }
})

const seriesList = computed(() => {
  return props.optionData.series
})
</script>

全局配置项GlobalSetting

<template>
  <collapse-item name="渲染器">
    <setting-item-box :alone="true">
      <template #name>
        <n-text>全局</n-text>
        <n-tooltip trigger="hover">
          <template #trigger>
            <n-icon size="21" :depth="3">
              <help-outline-icon></help-outline-icon>
            </n-icon>
          </template>
          <n-text>所有echarts图表组件默认都将采用所选的渲染器进行渲染</n-text>
        </n-tooltip>
      </template>
      <EchartsRendererSetting v-model="themeSetting.renderer" />
    </setting-item-box>
    <setting-item-box :alone="true">
      <template #name>
        <n-text>当前</n-text>
        <n-tooltip trigger="hover">
          <template #trigger>
            <n-icon size="21" :depth="3">
              <help-outline-icon></help-outline-icon>
            </n-icon>
          </template>
          <n-text>仅当前组件采用指定渲染器渲染</n-text>
        </n-tooltip>
      </template>
      <EchartsRendererSetting v-model="optionData.renderer" includeInherit />
    </setting-item-box>
  </collapse-item>
  <collapse-item v-if="title" name="标题">
    <template #header>
      <n-switch v-model:value="title.show" size="small"></n-switch>
    </template>
    <setting-item-box name="标题">
      <setting-item name="颜色">
        <n-color-picker v-model:value="title.textStyle.color" size="small"></n-color-picker>
      </setting-item>
      <setting-item name="大小">
        <n-input-number v-model:value="title.textStyle.fontSize" :min="1" size="small"></n-input-number>
      </setting-item>
    </setting-item-box>
    <setting-item-box name="副标题">
      <setting-item name="颜色">
        <n-color-picker size="small" v-model:value="title.subtextStyle.color"></n-color-picker>
      </setting-item>
      <setting-item name="大小">
        <n-input-number v-model:value="title.subtextStyle.fontSize" :min="1" size="small"></n-input-number>
      </setting-item>
    </setting-item-box>
  </collapse-item>

  <collapse-item v-if="grid" name="容器">
    <setting-item-box name="距离">
      <setting-item name="左侧距离">
        <n-input v-model:value="grid.left" size="small"></n-input>
      </setting-item>
      <setting-item name="右侧距离">
        <n-input v-model:value="grid.right" size="small"></n-input>
      </setting-item>
      <setting-item name="上侧距离">
        <n-input v-model:value="grid.top" size="small"></n-input>
      </setting-item>
      <setting-item name="下侧距离">
        <n-input v-model:value="grid.bottom" size="small"></n-input>
      </setting-item>
    </setting-item-box>
  </collapse-item>

  <collapse-item v-if="xAxis" name="X轴">
    <template #header>
      <n-switch v-model:value="xAxis.show" size="small"></n-switch>
    </template>
    <setting-item-box name="单位">
      <setting-item name="名称">
        <n-input v-model:value="xAxis.name" size="small"></n-input>
      </setting-item>
      <setting-item name="颜色">
        <n-color-picker size="small" v-model:value="xAxis.nameTextStyle.color"></n-color-picker>
      </setting-item>
      <setting-item name="大小">
        <n-input-number v-model:value="xAxis.nameTextStyle.fontSize" :min="12" size="small"></n-input-number>
      </setting-item>
      <setting-item name="偏移量">
        <n-input-number v-model:value="xAxis.nameGap" :min="5" size="small"></n-input-number>
      </setting-item>
    </setting-item-box>
    <setting-item-box name="标签">
      <setting-item name="展示">
        <n-space>
          <n-switch v-model:value="xAxis.axisLabel.show" size="small"></n-switch>
        </n-space>
      </setting-item>
      <setting-item name="颜色">
        <n-color-picker size="small" v-model:value="xAxis.axisLabel.color"></n-color-picker>
      </setting-item>
      <setting-item name="大小">
        <n-input-number v-model:value="xAxis.axisLabel.fontSize" :min="8" size="small"></n-input-number>
      </setting-item>
      <setting-item name="偏移量">
        <n-input-number v-model:value="xAxis.axisLabel.rotate" :min="-90" :max="90" size="small"></n-input-number>
      </setting-item>
    </setting-item-box>
    <setting-item-box name="轴线">
      <setting-item name="展示">
        <n-space>
          <n-switch v-model:value="xAxis.axisLine.show" size="small"></n-switch>
        </n-space>
      </setting-item>
      <setting-item name="颜色">
        <n-color-picker v-model:value="xAxis.axisLine.lineStyle.color" size="small"></n-color-picker>
      </setting-item>
      <setting-item name="粗细">
        <n-input-number v-model:value="xAxis.axisLine.lineStyle.width" :min="1" size="small"></n-input-number>
      </setting-item>
      <setting-item name="位置">
        <n-select v-model:value="xAxis.position" size="small" :options="axisConfig.xposition"></n-select>
      </setting-item>
      <setting-item name="对齐零">
        <n-space>
          <n-switch v-model:value="xAxis.axisLine.onZero" size="small"></n-switch>
        </n-space>
      </setting-item>
      <setting-item name="反向">
        <n-space>
          <n-switch v-model:value="xAxis.inverse" size="small"></n-switch>
        </n-space>
      </setting-item>
    </setting-item-box>
    <setting-item-box name="刻度">
      <setting-item name="展示">
        <n-space>
          <n-switch v-model:value="xAxis.axisTick.show" size="small"></n-switch>
        </n-space>
      </setting-item>
      <setting-item name="长度">
        <n-input-number v-model:value="xAxis.axisTick.length" :min="1" size="small"></n-input-number>
      </setting-item>
    </setting-item-box>
    <setting-item-box name="分割线">
      <setting-item name="展示">
        <n-space>
          <n-switch v-model:value="xAxis.splitLine.show" size="small"></n-switch>
        </n-space>
      </setting-item>
      <setting-item name="颜色">
        <n-color-picker v-model:value="xAxis.splitLine.lineStyle.color" size="small"></n-color-picker>
      </setting-item>
      <setting-item name="粗细">
        <n-input-number v-model:value="xAxis.splitLine.lineStyle.width" :min="1" size="small"></n-input-number>
      </setting-item>
      <setting-item name="类型">
        <n-select
          v-model:value="xAxis.splitLine.lineStyle.type"
          size="small"
          :options="axisConfig.splitLint.lineStyle.type"
        ></n-select>
      </setting-item>
    </setting-item-box>
  </collapse-item>

  <collapse-item v-if="yAxis" name="Y轴">
    <template #header>
      <n-switch v-model:value="yAxis.show" size="small"></n-switch>
    </template>
    <setting-item-box name="单位">
      <setting-item name="名称">
        <n-input v-model:value="yAxis.name" size="small"></n-input>
      </setting-item>
      <setting-item name="颜色">
        <n-color-picker size="small" v-model:value="yAxis.nameTextStyle.color"></n-color-picker>
      </setting-item>
      <setting-item name="大小">
        <n-input-number v-model:value="yAxis.nameTextStyle.fontSize" :min="8" size="small"></n-input-number>
      </setting-item>
      <setting-item name="偏移量">
        <n-input-number v-model:value="yAxis.nameGap" :min="5" size="small"></n-input-number>
      </setting-item>
    </setting-item-box>
    <setting-item-box name="标签">
      <setting-item name="展示">
        <n-space>
          <n-switch v-model:value="yAxis.axisLabel.show" size="small"></n-switch>
        </n-space>
      </setting-item>
      <setting-item name="颜色">
        <n-color-picker size="small" v-model:value="yAxis.axisLabel.color"></n-color-picker>
      </setting-item>
      <setting-item name="大小">
        <n-input-number v-model:value="yAxis.axisLabel.fontSize" :min="8" size="small"></n-input-number>
      </setting-item>
      <setting-item name="偏移量">
        <n-input-number v-model:value="yAxis.axisLabel.rotate" :min="-90" :max="90" size="small"></n-input-number>
      </setting-item>
    </setting-item-box>
    <setting-item-box name="轴线">
      <setting-item name="展示">
        <n-space>
          <n-switch v-model:value="yAxis.axisLine.show" size="small"></n-switch>
        </n-space>
      </setting-item>
      <setting-item name="颜色">
        <n-color-picker v-model:value="yAxis.axisLine.lineStyle.color" size="small"></n-color-picker>
      </setting-item>
      <setting-item name="粗细">
        <n-input-number v-model:value="yAxis.axisLine.lineStyle.width" :min="1" size="small"></n-input-number>
      </setting-item>
      <setting-item name="位置">
        <n-select v-model:value="yAxis.position" size="small" :options="axisConfig.yposition"></n-select>
      </setting-item>
      <setting-item name="对齐零">
        <n-space>
          <n-switch v-model:value="yAxis.axisLine.onZero" size="small"></n-switch>
        </n-space>
      </setting-item>
      <setting-item name="反向">
        <n-space>
          <n-switch v-model:value="yAxis.inverse" size="small"></n-switch>
        </n-space>
      </setting-item>
    </setting-item-box>
    <setting-item-box name="刻度">
      <setting-item name="展示">
        <n-space>
          <n-switch v-model:value="yAxis.axisTick.show" size="small"></n-switch>
        </n-space>
      </setting-item>
      <setting-item name="长度">
        <n-input-number v-model:value="yAxis.axisTick.length" :min="1" size="small"></n-input-number>
      </setting-item>
    </setting-item-box>
    <setting-item-box name="分割线">
      <setting-item name="展示">
        <n-space>
          <n-switch v-model:value="yAxis.splitLine.show" size="small"></n-switch>
        </n-space>
      </setting-item>
      <setting-item name="颜色">
        <n-color-picker v-model:value="yAxis.splitLine.lineStyle.color" size="small"></n-color-picker>
      </setting-item>
      <setting-item name="粗细">
        <n-input-number v-model:value="yAxis.splitLine.lineStyle.width" :min="1" size="small"></n-input-number>
      </setting-item>
      <setting-item name="类型">
        <n-select
          v-model:value="yAxis.splitLine.lineStyle.type"
          size="small"
          :options="axisConfig.splitLint.lineStyle.type"
        ></n-select>
      </setting-item>
    </setting-item-box>
  </collapse-item>

  <collapse-item v-if="legend" name="图例">
    <template #header>
      <n-switch v-model:value="legend.show" size="small"></n-switch>
    </template>
    <setting-item-box name="图例文字">
      <setting-item name="颜色">
        <n-color-picker size="small" v-model:value="legend.textStyle.color"></n-color-picker>
      </setting-item>
      <setting-item name="大小">
        <n-input-number v-model:value="legend.textStyle.fontSize" :min="1" size="small"></n-input-number>
      </setting-item>
    </setting-item-box>
    <setting-item-box name="图例位置">
      <setting-item name="x轴">
        <n-select v-model:value="legend.x" size="small" :options="legendConfig.lengendX" />
      </setting-item>
      <setting-item name="y轴">
        <n-select v-model:value="legend.y" size="small" :options="legendConfig.lengendY" />
      </setting-item>
    </setting-item-box>
    <setting-item-box name="图例信息">
      <setting-item name="方向">
        <n-select v-model:value="legend.orient" size="small" :options="legendConfig.orient" />
      </setting-item>
      <setting-item name="形状">
        <n-select v-model:value="legend.icon" size="small" :options="legendConfig.shape" />
      </setting-item>
    </setting-item-box>
    <setting-item-box name="图例大小">
      <setting-item name="宽">
        <n-input-number v-model:value="legend.itemWidth" :min="1" size="small"></n-input-number>
      </setting-item>
      <setting-item name="高">
        <n-input-number v-model:value="legend.itemHeight" :min="1" size="small"></n-input-number>
      </setting-item>
    </setting-item-box>
  </collapse-item>

  <collapse-item v-if="visualMap" name="视觉映射">
    <template #header>
      <n-switch v-model:value="visualMap.show" size="small"></n-switch>
    </template>

    <setting-item-box name="范围">
      <setting-item name="最小值">
        <n-input-number v-model:value="visualMap.min" size="small"></n-input-number>
      </setting-item>
      <setting-item name="最大值">
        <n-input-number v-model:value="visualMap.max" size="small"></n-input-number>
      </setting-item>
    </setting-item-box>

    <setting-item-box name="颜色">
      <setting-item :name="`层级-${index + 1}`" v-for="(item, index) in visualMap.inRange.color" :key="index">
        <n-color-picker v-model:value="visualMap.inRange.color[index]" size="small"></n-color-picker>
      </setting-item>
    </setting-item-box>

    <setting-item-box name="控制块">
      <setting-item name="放置方向">
        <n-select v-model:value="visualMap.orient" size="small" :options="axisConfig.visualMap.orient"></n-select>
      </setting-item>
      <setting-item name="宽度">
        <n-input-number v-model:value="visualMap.itemWidth" :min="5" size="small"></n-input-number>
      </setting-item>
      <setting-item name="高度">
        <n-input-number v-model:value="visualMap.itemHeight" :min="5" size="small"></n-input-number>
      </setting-item>
      <setting-item name="反转">
        <n-space>
          <n-switch v-model:value="visualMap.inverse" size="small"></n-switch>
        </n-space>
      </setting-item>
      <setting-item name="拖拽组件实时更新">
        <n-space>
          <n-switch v-model:value="visualMap.realtime" size="small"></n-switch>
        </n-space>
      </setting-item>
    </setting-item-box>
    <global-setting-position :targetData="visualMap"></global-setting-position>
  </collapse-item>
</template>

<script setup lang="ts">
import { PropType, computed, watch } from 'vue'
import { GlobalThemeJsonType } from '@/settings/chartThemes/index'
import { axisConfig, legendConfig } from '@/packages/chartConfiguration/echarts/index'
import { CollapseItem, SettingItemBox, SettingItem, GlobalSettingPosition } from '@/components/Pages/ChartItemSetting'
import { icon } from '@/plugins'
import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore'
import EchartsRendererSetting from './EchartsRendererSetting.vue'

const { HelpOutlineIcon } = icon.ionicons5

const props = defineProps({
  optionData: {
    type: Object as PropType<GlobalThemeJsonType>,
    required: true
  },
  inChart: {
    type: Boolean,
    required: false,
    default: false
  }
})

const chartEditStore = useChartEditStore()
const themeSetting = computed(() => {
  const chartThemeSetting = chartEditStore.getEditCanvasConfig.chartThemeSetting
  return chartThemeSetting
})

const title = computed(() => {
  return props.optionData.title
})

const xAxis = computed(() => {
  return props.optionData.xAxis
})

const yAxis = computed(() => {
  return props.optionData.yAxis
})

const legend = computed(() => {
  return props.optionData.legend
})

const grid = computed(() => {
  return props.optionData.grid
})

const visualMap = computed(() => {
  return props.optionData.visualMap
})

// 监听legend color颜色改变type = scroll的颜色
watch(() => legend.value && legend.value.textStyle.color, (newVal) => {
  if (legend.value && newVal) {
    legend.value.pageTextStyle.color = newVal
  } 
}, {
  immediate: true,
  deep: true,
})
</script>


对于Bar的页面文件,有如下所示的结构

<template>
  <v-chart
    ref="vChartRef"
    :init-options="initOptions"
    :theme="themeColor"
    :option="option"
    :manual-update="isPreview()"
    :update-options="{
      replaceMerge: replaceMergeArr,
    }"
    autoresize
  ></v-chart>
</template>

<script setup lang="ts">
import { ref, nextTick, computed, watch, PropType } from 'vue';
import VChart from 'vue-echarts';
import { useCanvasInitOptions } from '@/hooks/useCanvasInitOptions.hook';
import { use } from 'echarts/core';
import { CanvasRenderer } from 'echarts/renderers';
import { BarChart } from 'echarts/charts';
import config, { includes, seriesItem } from './config';
import { mergeTheme } from '@/packages/public/chart';
import { useChartDataFetch } from '@/hooks';
// import { CreateComponentType } from '@/packages/index.d';
import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore';
import { isPreview } from '@/utils';
import {
  DatasetComponent,
  GridComponent,
  TooltipComponent,
  LegendComponent,
} from 'echarts/components';
import isObject from 'lodash/isObject';
import cloneDeep from 'lodash/cloneDeep';

const props = defineProps({
  themeSetting: {
    type: Object,
    required: true,
  },
  themeColor: {
    type: Object,
    required: true,
  },
  chartConfig: {
    type: Object as PropType<config>,
    required: true,
  },
});

const initOptions = useCanvasInitOptions(props.chartConfig.option, props.themeSetting);

use([DatasetComponent, CanvasRenderer, BarChart, GridComponent, TooltipComponent, LegendComponent]);

const replaceMergeArr = ref<string[]>();

const option = computed(() => {
  return mergeTheme(props.chartConfig.option, props.themeSetting, includes);
});

// dataset 无法变更条数的补丁
watch(
  () => props.chartConfig.option.dataset,
  (newData: { dimensions: any }, oldData) => {
    try {
      if (!isObject(newData) || !('dimensions' in newData)) return;
      if (Array.isArray(newData?.dimensions)) {
        const seriesArr = [];
        // 对oldData进行判断,防止传入错误数据之后对旧维度判断产生干扰
        // 此处计算的是dimensions的Y轴维度,若是dimensions.length为0或1,则默认为1,排除X轴维度干扰
        const oldDimensions =
          Array.isArray(oldData && oldData.dimensions) && oldData.dimensions.length >= 1
            ? oldData.dimensions.length
            : 1;
        const newDimensions = newData.dimensions.length >= 1 ? newData.dimensions.length : 1;
        const dimensionsGap = newDimensions - oldDimensions;
        if (dimensionsGap < 0) {
          props.chartConfig.option.series.splice(newDimensions - 1);
        } else if (dimensionsGap > 0) {
          if (
            !oldData ||
            (!oldData && oldData.dimensions) ||
            !Array.isArray(oldData && oldData.dimensions) ||
            (!oldData && oldData.dimensions.length)
          ) {
            props.chartConfig.option.series = [];
          }
          for (let i = 0; i < dimensionsGap; i++) {
            seriesArr.push(cloneDeep(seriesItem));
          }
          props.chartConfig.option.series.push(...seriesArr);
        }
        replaceMergeArr.value = ['series'];
        nextTick(() => {
          replaceMergeArr.value = [];
        });
      }
    } catch (error) {
      console.log(error);
    }
  },
  {
    deep: false,
  },
);

const { vChartRef } = useChartDataFetch(props.chartConfig, useChartEditStore);
</script>