Skip to content

大屏组件开发

1. 大屏自定义渲染组件开发

// 参考案例
<template>
  <div class="h-full w-full bg-cover bg-no-repeat overflow-hidden" :style="getStyle">
    <div ref="barCommonRef" class="relative h-full w-full"></div>
  </div>
</template>

<script setup lang="ts">
import { DesignApi } from '@/api'
import { cloneDeep, get } from 'lodash-es'
import { onBeforeMount, ref, inject, computed, onMounted, nextTick, watch, onUnmounted } from 'vue'
import { getNodeById, newFunctionHandle } from '@/components/screen-component/utils/commonUtils'
import { formatDatasetAxis, compareData, buildEventParam } from '@/components/screen-component/utils/formatDataset'
import * as echarts from 'echarts'
import 'echarts-liquidfill'
import { useResizeObserver } from '@vueuse/core'

const yAxis = ref([])
const aliasList = ref([])
const barCommonRef = ref(null)
const chartRender = ref()

const getNode = inject('getNode') as Function
const getGraph = inject('getGraph') as Function
const emits = defineEmits(['screenChange'])
const nodeData = computed(() => getNode && getNode() && getNode())

const nodeInfo = ref<Any>({})
const nodeId = ref('')
const getNodeData = () => {
  nodeInfo.value = getNode().getData()
  nodeId.value = getNode().id
}

useResizeObserver(barCommonRef, () => {
  chartRender.value?.resize()
})
watch(
  () => nodeInfo.value,
  () => {
    if (
      !compareData(yAxis.value, nodeInfo.value?.dataOption?.yAxis) ||
      !compareData(aliasList.value, nodeInfo.value?.dataOption?.aliasList)
    ) {
      initChart()
    } else {
      setOption()
    }
  },
  {
    deep: true,
  }
)
const renderChart = (option: Any) => {
  // 渲染前处理数据
  const rendChart = nodeInfo.value?.eventOption?.eventList.find((event) => event.name === 'beforeRender')
  if (rendChart) {
    // 处理数据逻辑
    // console.log('beforeRender====>', option)
  }
  return option
}
const setOption = () => {
  if (!chartRender.value) {
    return
  }
  const { renderer, ...cloneOption } = nodeInfo.value.option || {}
  const option = formatDatasetAxis(cloneOption, nodeInfo.value.dataOption)
  const renderOption = renderChart(option)
  chartRender.value.setOption({
    ...(renderOption || option),
  })
}

const initChart = () => {
  if (chartRender.value) {
    chartRender.value.dispose()
  }
  if (!barCommonRef.value) {
    return
  }
  const { renderer, ...cloneOption } = nodeInfo.value.option || {}
  yAxis.value = cloneDeep(nodeInfo.value.dataOption.yAxis)
  aliasList.value = cloneDeep(nodeInfo.value.dataOption.aliasList)
  const eventList = nodeInfo.value?.eventOption?.eventList || []
  const chart = echarts.init(barCommonRef.value, null, renderer)
  const option = formatDatasetAxis(cloneOption, nodeInfo.value.dataOption)
  const renderOption = renderChart(option)
  chart.setOption(
    {
      ...(renderOption || option),
    },
    true
  )
  eventList
    ?.filter((e) => e.name !== 'beforeRender')
    .forEach((event) => {
      chart.on(event.name, (params) => {
        updateNodeData(event, params)
      })
    })

  // 数据联动逻辑处理
  if (
    nodeInfo.value?.dataOption?.linkageDataList?.length > 0 ||
    nodeInfo.value?.option?.toScreen?.name ||
    nodeInfo.value.option?.toScreen?.name
  ) {
    chart.on('click', (params) => {
      const event = buildEventParam(nodeInfo.value?.dataOption?.linkageDataList)
      updateNodeData(event, params)
    })
  }
  chartRender.value = chart
}

// 背景颜色
const getStyle = computed(() => {
  let str = ''
  // 背景色
  if (nodeInfo.value.option.renderBackgroundColor) {
    str += `background-color: ${nodeInfo.value.option.renderBackgroundColor};`
  }
  // 背景图片
  if (nodeInfo.value.option?.renderBackgroundUrl) {
    str += `background-image: url('/api/magus-screen/screen/file/download?path=${nodeInfo.value.option.renderBackgroundUrl}');`
  }
  if (nodeInfo.value.option?.renderOpacity) {
    str += `opacity: ${nodeInfo.value.option.renderOpacity};`
  }
  return str
})
const size = ref({})
const sizeChange = () => {
  size.value = getNode().getSize()
}
const readonly = computed(() => nodeInfo.value.readonly)

const setDataOption = (nodeData: Any, params: Any, assign: Any) => {
  const dataOption = cloneDeep(nodeData?.dataOption)
  let flag = false
  const dataSearchItem = dataOption.dataSetSearch.find((item) => item.name === assign.searchName)
  // 最后设置的值
  const setValue = get(params, assign.key)
  if (dataSearchItem && dataSearchItem.value !== setValue) {
    dataSearchItem.value = setValue
    flag = true
  }
  return { dataOption, flag }
}
const successCallBack = (data) => {
  console.log('successCallBack', data)
}
const errorCallBack = (e) => {
  console.log('errorCallBack', e)
}

const toScreenInfo = (params: AnyObject) => {
  const toScreen = nodeInfo.value?.option?.toScreen || {}
  const data = toScreen?.data?.map((item) => {
    return {
      ...item,
      field: get(params, item.field),
    }
  })
  emits('screenChange', { ...toScreen, data })
}
// 处理事件
const updateNodeData = ({ event }, params) => {
  if (readonly.value) return
  toScreenInfo(params)
  event.forEach((eve: Any) => {
    // 变更数据
    if (eve.type === 'assign') {
      if (Array.isArray(eve.assign)) {
        eve?.assign.forEach((item) => {
          const node = getNodeById(getGraph(), item.dataId as string)
          if (node) {
            const nodeData = node.getData()
            const { dataOption, flag } = setDataOption(nodeData, params, item)
            if (flag) {
              node.updateData({ ...nodeData, dataOption })
            }
          }
        })
      } else {
        const node = getNodeById(getGraph(), eve?.assign?.dataId as string)
        if (node) {
          const nodeData = node.getData()
          const { dataOption, flag } = setDataOption(nodeData, params, eve.assign)
          if (flag) {
            node.updateData({ ...nodeData, dataOption })
          }
        }
      }
    } else {
      const res = {}
      newFunctionHandle(params, res, eve.script, true, successCallBack, errorCallBack)
    }
  })
}

onMounted(() => {
  nodeData.value.on('change:data', ({ current }) => {
    getNodeData()
  })
  nodeData.value.on('change:size', () => {
    sizeChange()
  })
  initChart()
})
onBeforeMount(() => {
  getNodeData()
  sizeChange()
})
</script>
  • 所有从节点数据中进行数据绑定请使用计算属性
// 例如案例中的样式
const getStyle = computed(() => {
  let str = ''
  // 背景色
  if (nodeInfo.value.option.renderBackgroundColor) {
    str += `background-color: ${nodeInfo.value.option.renderBackgroundColor};`
  }
  // 背景图片
  if (nodeInfo.value.option?.renderBackgroundUrl) {
    str += `background-image: url('/api/magus-screen/screen/file/download?path=${nodeInfo.value.option.renderBackgroundUrl}');`
  }
  if (nodeInfo.value.option?.renderOpacity) {
    str += `opacity: ${nodeInfo.value.option.renderOpacity};`
  }
  return str
})
  • 获取画布实例
const getGraph = inject('getGraph') as Function
const graph = getGraph()
  • 获取节点实例与实例数据
const getNode = inject('getNode') as Function
const node = getNode() // 节点实例
const nodeData = node.getData()

// 如果要使用内置数据集的数据,请取 nodeData.dataOption.data
const data = computed(() => nodeData?.dataOption?.data || [])
  • 触发大屏跳转的方法并携带参数
emits('screenChange', {
 name: '', // 跳转的大屏名称
 data: [{
	filed: '', // 传过去的值
	target: [['id', 'key'], ['id', 'key']],  // 批量需要修改的组件节点的id和对应查询参数(data.dataOption.dataSetSearch)的key
	}]
})

2. 大屏自定义渲染组件属性面板开发

  • 获取画布实例
const graph = useGraphInstance()
  • 获取节点实例与实例数据
const node = useCurrentNode()
const data = node.value?.getData()
// 如果要使用内置数据集的数据,data的结构只有有这几项
data: {
	dataOption: {
		type: '', //进行数据集查询的类型标识 sql,api,lot
		data: [], // 数据存储位置
		dataSetSearch: [{name: '', defaultValue: ''}], //数据查询参数
	} 
}
  • 父节点传递的数据集列表
const props = defineProps<{ dataSourceList: Array}>()

3.打包文件配置

import propComp from './config.vue'
import webCom from './bar.vue'
import {
  option,
  defaultSize,
  dataOption,
  eventOption,
} from '@/components/screen-component/Charts/Bars/BarCommon/config'

const comData = {
  option,
  defaultSize,
  dataOption,
  eventOption,
}

export { webCom, comData, propComp }
// webCom 打包文件抛出来的渲染组件
// propComp 打包文件抛出来的属性面板组件 (当所有的组件都在组件内部完成是可没有)
// comData 组件的默认值