低代码:如何实现不同组件间的级联通信(进阶版)

前言

上文里,我们使用事件触发器,实现组件间的级联通信,这种通信方式缺点如下所示:

  1. 事件触发时,需指明事件参数
  2. 随着项目的扩大,代码会变得越来越难以维护

基于上述考虑,我参考了另一开源代码的方案,重构了组件间的级联方案,大致方案步骤如下:

  1. 存储页面正在编辑的所有组件至chartEditStore内;
  2. 当前组件A,添加事件eventA时,选择联动组件B
  3. 在事件eventA内,将组件A选择的值绑定到组件B的接口请求参数内;
  4. 联动组件B接收到变化后的请求参数,重新请求接口,便能实现组件间的联动刷新效果

编辑效果如下图所示:

左侧组件为A,右侧组件为B 72516e6ce27141a788a4eb3b6475e0b8.png

源码地址

存储编辑的组件

在组件拖拽或双击时,将其添加到数组componentList

创建chartEditStore.ts,在内部增加addComponentList方法,存储新增的组件列表

   /**
     * * 新增组件列表
     * @param componentInstance 新图表实例
     * @param isHead 是否头部插入
     * @param isHistory 是否进行记录
     * @returns
     */
    addComponentList(
      componentInstance:
        | CreateComponentType
        | CreateComponentGroupType
        | Array<CreateComponentType | CreateComponentGroupType>,
      isHead = false,
      isHistory = false
    ): void {
      if (componentInstance instanceof Array) {
        componentInstance.forEach(item => {
          this.addComponentList(item, isHead, isHistory)
        })
        return
      }
      if (isHistory) {
        chartHistoryStore.createAddHistory([componentInstance])
      }
      if (isHead) {
        this.componentList.unshift(componentInstance)
        return
      }
      this.componentList.push(componentInstance)
    }

getters增加getComponentList方法,获取到所有的组件列表信息

getComponentList(): Array<CreateComponentType | CreateComponentGroupType> {
    return this.componentList
}

选择联动组件

点击增加联动绑定

在组件A内的点击事件内,增加联动绑定功能,如下代码所示:

<template>
        <div
          :class="['right-item', conActive == index ? 'active' : '']"
          v-for="(item, index) in dirList.children"
          :key="index"
          @click="onClickDown(item, index)"
        >
          {{ item.name }}
        </div>
</template>
<script lang="ts" setup>
import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore'
import { useChartInteract } from '@/hooks'
const onClickDown = (item: any, index: number) => {
  const { key } = item
  // 存储到联动数据
  useChartInteract(
    props.chartConfig,
    useChartEditStore,
    { [ComponentInteractParamsEnum.DATA]: key },
    InteractEventOn.CLICK
  )
}
</script>

useChartInteract方法内,我们将变化后的key值赋值到B组件的请求参数Params内,它的各个参数的含义如下所示:

  1. props.chartConfig:组件A的配置信息,我们将该组件所有的事件信息配置在该参数的events属性内,我们将联动事件定义为interactEvents,它的默认初始值[],但在组件A内,我们给这其添加了click事件,代码如下所示:
// 定义组件触发回调事件
export const interactActions: InteractActionsType[] = [
  {
    interactType: InteractEventOn.CLICK,
    interactName: '点击',
    componentEmitEvents: {
      [ComponentInteractEventEnum.DATA]: [
        {
          value: ComponentInteractParamsEnum.DATA,
          label: '类型'
        }
      ]
    }
  }
]
  1. useChartEditStore:存储当前编辑页面所有的组件列表
  2. { [ComponentInteractParamsEnum.DATA]: key }ComponentInteractParamsEnum.DATA的值为字符串data,该参数可简单理解为{'data': key},后期组件B的请求参数重新赋值,来源于该对象的key
  3. InteractEventOn.CLICK:即为事件类型,这里默认指字符串click

useChartInteract代码的具体功能,我们在第三节的内容里进行分析

增加组件交互配置功能

在右侧事件编辑面板,增加组件交互配置功能,部分代码如下

<template>
  <n-collapse-item title="组件交互" name="1" v-if="interactActions.length">
      ...
     <setting-item-box name="触发事件" :alone="true">
        <n-input-group v-if="interactActions">
          <n-select
            class="select-type-options"
            v-model:value="item.interactOn"
            size="tiny"
            :options="interactActions"
          />
        </n-input-group>
      </setting-item-box>
      <setting-item-box :alone="true">
          ...
          <n-text>绑定</n-text>
          ...
          <n-select
              class="select-type-options"
              value-field="id"
              label-field="title"
              size="tiny"
              filterable
              placeholder="仅展示符合条件的组件"
              v-model:value="item.interactComponentId"
              :options="fnEventsOptions()"
            />
      </setting-item-box>
  </n-collapse-item>
</template>

interactActions即我们之前配置在组件A内的联动事件信息,我们只配置了一个click事件,故在触发事件的下拉选择框内,我们只看到了点击类型,如下图所示:

ccda48b707e442c4a813d95db3d8b9ad.png

fnEventsOptions过滤非自己等信息,只显示其他组件,代码如下所示:

// 绑定组件列表
const fnEventsOptions = (): Array<SelectOption | SelectGroupOption> => {
  const filterOptionList = chartEditStore.componentList.filter(item => {
    // 排除自己
    const isNotSelf = item.id !== targetData.value.id
    // 排除静态组件
    const isNotStatic = item.chartConfig.chartFrame !== ChartFrameEnum.STATIC
    // 排除分组
    const isNotGroup = !item.isGroup

    return isNotSelf && isNotStatic && isNotGroup
  })
  
  return filterOptionList

选择了绑定组件B后,我们将它的componentId存储到interactComponentId内,代码为v-model:value="item.interactComponentId"

编辑页面,除了组件A,便是组件B,故绑定下拉列表里,只有组件B选项

ca2b7bc2bfbb4bb8b35eb33cf3d6c745.png

绑定值至联动组件的请求参数内

让我们一起看下useChartInteract内的代码

// Params 参数修改触发 api 更新图表请求
export const useChartInteract = (
  chartConfig: CreateComponentType,
  useChartEditStore: ChartEditStoreType,
  param: { [T: string]: any },
  interactEventOn: string
) => {
  const chartEditStore = useChartEditStore()
  const { interactEvents } = chartConfig.events
  const fnOnEvent = interactEvents.filter(item => {
    return item.interactOn === interactEventOn
  })

  if (fnOnEvent.length === 0) return
  fnOnEvent.forEach(item => {
    const index = chartEditStore.fetchTargetIndex(item.interactComponentId)
    if (index === -1) return
    const component = chartEditStore.componentList[index]
    const { Params, Header } = toRefs(component!.request.requestParams)

    // 处理管理组件请求参数
    Object.keys(item.interactFn).forEach(key => {
      if (Params.value[key]) {
        Params.value[key] = param[item.interactFn[key]] || Params.value[key]
      }
      if (Header.value[key]) {
        Header.value[key] = param[item.interactFn[key]]
      }
    })
  })
}

我们从组件AchartConfigevents属性内,拿到interactEvents事件数组,遍历该数组,找到类型为click(即interactEventOn参数的值)的事件fnOnEvent

fnOnEvent进行遍历,从chartEditStore找到组件ID为interactComponentId的组件,通过toRefs从组件内的requestParams得到当前组件B的参数配置信息,使用toRefs获得到的ParamsHeader具有响应式效果,即值变化,会触发请求

将函数参数param对象{'data': key}中的key值赋值给组件BParams内对应的参数上,组件B的接口请求配置,如下图所示:

26f24b0bda6f428bb9121703a00f67c6.png

参数变化,重新请求接口

在组件B内增加如下方法

<template>
  <v-chart ref="vChartRef" :init-options="initOptions" :theme="themeColor" :option="option.value" :manual-update="isPreview()" autoresize>
  </v-chart>
</template>
<script setup lang="ts">
import VChart from 'vue-echarts'
import { useChartDataFetch } from '@/hooks'

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

我们通过useChartDataFetch方法实现接口请求,接下来,看看这个方法做了什么呢?

/**
 * setdata 数据监听与更改
 * @param targetComponent
 * @param useChartEditStore 若直接引会报错,只能动态传递
 * @param updateCallback 自定义更新函数
 */
export const useChartDataFetch = (
  targetComponent: CreateComponentType,
  useChartEditStore: ChartEditStoreType,
  updateCallback?: (...args: any) => any
) => {
   // eCharts 组件配合 vChart 库更新方式
  const echartsUpdateHandle = (dataset: any) => {
    if (chartFrame === ChartFrameEnum.ECHARTS) {
      if (vChartRef.value) {
        setOption(vChartRef.value, { dataset: dataset })
      }
    }
  }
  
  const requestIntervalFn = () => {
    const chartEditStore = useChartEditStore()

    // 全局数据
    const {
      requestOriginUrl,
      requestIntervalUnit: globalUnit,
      requestInterval: globalRequestInterval
    } = toRefs(chartEditStore.getRequestGlobalConfig)

    // 目标组件
    const {
      requestDataType,
      requestUrl,
      requestIntervalUnit: targetUnit,
      requestInterval: targetInterval
    } = toRefs(targetComponent.request)

    // 非请求类型
    if (requestDataType.value !== RequestDataTypeEnum.AJAX) return

    try {
      // 处理地址
      // @ts-ignore
      if (requestUrl?.value) {
        // requestOriginUrl 允许为空
        const completePath = requestOriginUrl && requestOriginUrl.value + requestUrl.value
        if (!completePath) return

        clearInterval(fetchInterval)

        const fetchFn = async () => {
          const res = await customizeHttp(toRaw(targetComponent.request), toRaw(chartEditStore.getRequestGlobalConfig), toRaw(targetComponent.id))
          if (res) {
            try {
              const filter = targetComponent.filter
              const { data } = res
              echartsUpdateHandle(newFunctionHandle(data, res, filter))
              // 更新回调函数
              if (updateCallback) {
                updateCallback(newFunctionHandle(data, res, filter))
              }
            } catch (error) {
              console.error(error)
            }
          }
        }

        // 普通初始化与组件交互处理监听
        watch(
          () => targetComponent.request,
          () => {
            fetchFn()
          },
          {
            immediate: true,
            deep: true
          }
        )

        // 定时时间
        const time = targetInterval && targetInterval.value ? targetInterval.value : globalRequestInterval.value
        // 单位
        const unit = targetInterval && targetInterval.value ? targetUnit.value : globalUnit.value
        // 开启轮询
        if (time) fetchInterval = setInterval(fetchFn, intervalUnitHandle(time, unit))
      }
      // eslint-disable-next-line no-empty
    } catch (error) {
      console.log(error)
    }
  }
  if (isPreview()) {
    // 判断是否是数据池类型
    targetComponent.request.requestDataType === RequestDataTypeEnum.Pond
      ? addGlobalDataInterface(targetComponent, useChartEditStore, updateCallback || echartsUpdateHandle)
      : requestIntervalFn()
  }
  return { vChartRef }
}

代码有些长,让我们自动忽略数据池类型判断,上面的方法定义了requestIntervalFn方法,并在最后执行了它,当轮询时间time为0时,只在图表初始化时请求,若time非0,则会根据设置的time时间轮询请求接口

requestIntervalFn方法内,定义了fetchFn方法,在该方法内部customizeHttp处理接口请求,监听targetComponent.request变化,即组件B的参数变化,重新执行fetchFn方法,实现接口再次调用

最终,实现的效果图如下所示:

35.gif

可以看到每次点击,会看到两次接口请求,第一次请求类型为preflight,即浏览器的预检请求,属于options请求。该请求会在浏览器认为即将要执行的请求可能会对服务器造成不可预知的影响时,由浏览器自动发出。

由于接口地址跨域,所以报了CORS错误信息,但这些并不重要,我们可以清楚地看到随着组件A的点击,请求的level参数值一直在变化。

若大家有更好的办法,欢迎在评论区内留言~~

全部评论

相关推荐

点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务