页面基础布局

在设计高度交互性和自定义性的可视化仪表盘时,页面通常被分为以下三个部分:

  • 左侧部分 — 组件选择区:在这里可以选取组件并拖拽到画布上进行展示;
  • 中间部分 — 仪表盘编辑展示区:通过拖拽左侧的组件,可以在这里对展示区域的组件进行拖拽、缩放、布局等操作,生成预期的布局效果;
  • 右侧部分 — 组件属性编辑区:当选中某一个组件后,可以通过预先开放的接口对组件的预置属性进行编辑操作。

每一个部分各司其职,共同构建用户最终所需的界面。这样的布局不仅提高工作的流畅性,也极大地提升了用户体验。

使用的第三方库

库名用途
@antv/g2图表渲染
grid-layout-plus拖拽、缩放布局(基于 Vue3)

页面具体实现

1. 创建数字图组件(BigNumber)

(1) 创建 BigNumber 文件夹,并在文件夹下创建 index.vue,实现一个数字图组件;

(2)settingConfig.js 中编写数字图组件对应的配置文件,开放字体大小以及字体权重的编辑入口。

components/
  BigNumber/
    index.vue          # 组件实现
    settingConfig.js   # 可编辑属性配置

2. 实现左侧组件列表展示

(1) 首先在 utils 文件夹下创建 const.js,用来定义组件库的组件列表:

// utils/const.js
export const COMPONENT_LIST = [
  { type: 'BigNumber', name: '数字图', icon: 'NumberOutlined' },
  // ... 其他组件
]

(2)LeftComponent.vue 组件中引入并渲染列表,对组件设置 draggable 属性开启拖拽功能,并绑定 data-type 以及 data-name 属性,以便确认用户拖拽的组件类型:

<template>
  <div class="left-panel">
    <div
      v-for="item in componentList"
      :key="item.type"
      draggable="true"
      :data-type="item.type"
      :data-name="item.name"
      @dragstart="handleDragStart"
    >
      {{ item.name }}
    </div>
  </div>
</template>

循环获取页面可拖拽的节点,添加拖拽事件监听,并通过 dataTransfer.setData() 存储当前拖拽组件的类型以及 config 配置:

function handleDragStart(event) {
  const type = event.target.dataset.type
  const config = getDefaultConfig(type)
  event.dataTransfer.setData('type', type)
  event.dataTransfer.setData('config', JSON.stringify(config))
}

3. 实现中间区域组件的拖拽缩放渲染功能

(1)获取拖拽的组件信息

RenderComponent.vue 组件中给根组件绑定 drop 事件,通过 dataTransfer.getData() 获取刚才存储的拖拽组件信息,并添加到画布的组件列表中:

function handleDrop(event) {
  const type = event.dataTransfer.getData('type')
  const config = JSON.parse(event.dataTransfer.getData('config'))
  // 计算放置位置并添加到布局列表
  layoutList.value.push({
    i: Date.now().toString(),
    x: 0, y: 0, w: 4, h: 3,
    type,
    config
  })
}

(2)实现可拖拽、缩放的布局功能

借助 Grid Layout Plus 插件实现(Grid Layout Plus 是一个基于 Vue3 的布局插件,支持组件的缩放、拖拽等功能,更多用法参考 官方文档):

<template>
  <GridLayout
    v-model:layout="layoutList"
    :col-num="24"
    :row-height="30"
    :is-draggable="true"
    :is-resizable="true"
    @drop="handleDrop"
  >
    <GridItem
      v-for="item in layoutList"
      :key="item.i"
      :i="item.i"
      :x="item.x" :y="item.y"
      :w="item.w" :h="item.h"
      @click="handleSelectItem(item)"
    >
      <RenderCard :type="item.type" :config="item.config" />
    </GridItem>
  </GridLayout>
</template>

由于组件库的组件会有多个,可以创建一个 RenderCard.vue 组件,统一引入组件库的所有组件,通过传递组件的 type 属性进行组件的展示:

<!-- RenderCard.vue -->
<template>
  <component :is="componentMap[type]" v-bind="config" />
</template>

<script setup>
import BigNumber from '@/components/BigNumber/index.vue'

const componentMap = {
  BigNumber,
  // ... 其他组件
}

defineProps(['type', 'config'])
</script>

4. 实现右侧区域的组件属性编辑

(1)实现点击事件

点击中间区域的组件,将组件信息传递给 RightComponent.vue 组件,并将 styleFormItem 中的数据动态渲染到 form 表单中:

// 在父组件中
const selectedItem = ref(null)

function handleSelectItem(item) {
  selectedItem.value = item
}
<!-- RightComponent.vue -->
<template>
  <div v-if="currentItem">
    <a-form>
      <a-form-item
        v-for="field in formItems"
        :key="field.key"
        :label="field.label"
      >
        <a-input
          v-if="field.type === 'input'"
          v-model:value="currentItem.config[field.key]"
        />
        <a-input-number
          v-else-if="field.type === 'number'"
          v-model:value="currentItem.config[field.key]"
        />
      </a-form-item>
    </a-form>
  </div>
</template>

(2)收集表单信息并更新组件渲染

收集表单信息并存储到当前组件的 styleOption 中,并触发组件重新渲染。至此,一个基础的可视化仪表盘设计器就实现了。

// 监听配置变化,实时更新渲染
watch(
  () => currentItem.value?.config,
  (newConfig) => {
    // 触发组件重新渲染
  },
  { deep: true }
)

页面效果展示

完成上述开发后,用户可以:

  1. 从左侧组件区拖拽任意图表组件到中间画布
  2. 在画布中自由调整组件的位置和大小
  3. 点击选中任意组件后,在右侧面板编辑字体大小、颜色等属性
  4. 实时预览最终效果,所见即所得

这套仪表盘设计器的架构具有良好的扩展性,后续可以继续添加折线图、柱状图、饼图等更多组件类型,只需在 const.js 中注册组件信息,并在 RenderCard.vue 中引入对应组件即可。