简介:一份基于Vue.js的大屏可视化搭建系统源码,面向需要构建数据展示大屏的前端开发者、数据可视化工程师及项目团队,重点解决自定义布局、图表地图混排与复杂交互的开发效率问题。压缩包内共1131个文件,以vue组件、js逻辑、png素材为主,辅以css样式、json配置及少量html/mp4说明,整体约28.61MB,目录结构可支撑直接预览与二次开发。目前已有2016人学习,适合具备一定Vue基础、希望快速搭建运营监控或指挥中心大屏的开发者参考。源码充分利用Vue的组件化、响应式数据绑定、指令与插槽机制,配合Vuex状态管理、Vue Router路由配置及ECharts等图表库集成,可实现图表、地图、仪表盘等可视化元素的灵活组合;通过虚拟DOM和懒加载优化,即使大数据量下也能保持流畅交互。开发者可基于现有组件扩展自定义大屏方案,也可借鉴其目录组织与接口封装思路,用于实际项目落地。
1. 大屏可视化搭建系统:拖拽生成数据看板,不写代码也能交付
可视化大屏这活儿,做过的人都知道有多磨人:设计稿改一版,前端调三天;数据口径变一下,又要重新对字段;客户临时要加个图表,整个页面布局都得挪。这套Vue搭建系统就是冲着这个痛点来的——通过拖拽组件、配置数据源、绑定字段,让运营或交付同学直接在浏览器里拼出一块可视化大屏,不需要手写一行布局代码。它的定位很明确:不是像ECharts那样只解决图表渲染的库,而是一整套“组件-画布-属性配置-数据绑定-预览发布”的搭建链路。适合两类人用:一类是前端开发者,可以拿它做二次开发,给团队搭一个内部可视化平台;另一类是懂业务但不懂代码的交付人员,纯粹当作生产力工具来配置大屏。下文我从技术栈选型讲到目录结构、核心模块实现、运行方式,最后给几条改造建议,尽量让新手能跑通、熟手能看懂设计思路。
2. 技术选型与整体设计:为什么是Vue3 + Vite + Pinia这套组合
2.1 框架选型背后的考量
这套源码基于Vue实现,核心栈为Vue 3 + Vite + Pinia。Vue 3的Composition API在搭建系统这种场景里特别合适——拖拽组件、配置面板、数据绑定各模块之间需要共享状态,用ref和computed拆逻辑比Options API清晰得多。构建工具选Vite而不是Webpack,主要图它冷启动快,大屏项目组件多、依赖重,Vite基于ESBuild的预构建在开发环境下几乎秒开,改代码热更新也是毫秒级。状态管理用Pinia替代Vuex,是因为它的模块化设计天生适合“画布状态、组件列表、数据源配置”这种按业务域拆store的场景,而且没有Vuex那套繁琐的mutation约定,写起来更直接。另外,系统内置了ECharts做图表渲染底座,所有图表组件本质上是对ECharts配置项的封装。
// stores/canvasStore.js 核心状态管理示例(简化) import { defineStore } from 'pinia' import { ref, computed } from 'vue' export const useCanvasStore = defineStore('canvas', () => { // 画布上的组件实例列表 const widgets = ref([]) // 当前选中的组件id const activeId = ref(null) // 画布尺寸,默认1920x1080,适配大屏分辨率 const canvasSize = ref({ width: 1920, height: 1080 }) const activeWidget = computed(() => widgets.value.find(item => item.id === activeId.value) ) function addWidget(widget) { const newWidget = { id: `widget_${Date.now()}`, ...widget, // 新组件默认放在画布中央,避免出现“拖进去就消失”的困惑 position: { x: canvasSize.value.width / 2, y: canvasSize.value.height / 2 } } widgets.value.push(newWidget) activeId.value = newWidget.id } function updateWidget(id, patch) { const idx = widgets.value.findIndex(item => item.id === id) if (idx > -1) { widgets.value[idx] = { ...widgets.value[idx], ...patch } } } function removeWidget(id) { widgets.value = widgets.value.filter(item => item.id !== id) if (activeId.value === id) activeId.value = null } return { widgets, activeId, canvasSize, activeWidget, addWidget, updateWidget, removeWidget } })这里有几个参数值得注意:canvasSize默认1920x1080,对应标准大屏分辨率;position初始在画布中央,这是我加的“防呆”处理——很多搭建系统新拖入的组件定位在左上角,用户要拖半天才能找到。activeWidget是个计算属性,后面属性面板直接绑定它,选中谁就编辑谁,不需要额外传参。
2.2 整体架构与插件化思路
系统的模块划分很清晰,分为四层:基础层(组件注册表、拖拽指令、渲染器)、状态层(Pinia各store)、业务层(组件面板、画布编辑器、属性配置面板、数据源管理)、应用层(预览、保存、导入导出)。组件注册表是这整套体系的灵魂,每个可视化组件在注册时声明自己的名称、图标、分类、默认尺寸、可配置属性,以及负责渲染的组件对象。
// core/registry.js 组件注册表核心逻辑 const registry = new Map() export function registerWidget(config) { if (!config.name || !config.component) { throw new Error('组件注册失败:name和component不能为空') } registry.set(config.name, config) } export function getWidgetConfig(name) { return registry.get(name) } export function getWidgetListByCategory(category) { const list = [] registry.forEach((config) => { if (config.category === category) list.push(config) }) return list } export function getAllCategories() { const categories = [] registry.forEach((config) => { if (!categories.includes(config.category)) { categories.push(config.category) } }) return categories }这个注册表决定了系统能扩展成什么样。后文我会讲怎么注册一个自定义组件,这里先明确:所有图表、边框、文字等组件都走同一套注册流程。相比之下,有些搭建系统用switch或if-else硬编码组件类型,新增一个图表要改渲染器、属性面板、组件列表三处代码,这种设计显然是给自己挖坑。
2.3 渲染器:把配置变成真实页面
组件注册完成之后,画布里的渲染器负责把配置对象render成真实DOM。这套系统使用了动态组件的方式进行渲染,核心代码:
<!-- components/WidgetRenderer.vue 画布渲染器 --> <template> <component :is="widget.component" v-bind="widget.props" :style="widgetStyle" :data-widget-id="widget.id" /> </template> <script setup> import { computed } from 'vue' const props = defineProps({ widget: { type: Object, required: true } }) // 根据widget配置计算绝对定位样式 const widgetStyle = computed(() => ({ position: 'absolute', left: props.widget.position.x + 'px', top: props.widget.position.y + 'px', width: props.widget.size.width + 'px', height: props.widget.size.height + 'px', // 拖拽时降低透明度,靠这一行配合拖拽指令就能实现“半透明跟随”效果 opacity: props.widget.dragging ? 0.7 : 1, transition: props.widget.dragging ? 'none' : 'all 0.1s ease' })) </script>这个渲染器是画布编辑器核心的核心:它就是画布组件树的直接映射。:is="widget.component"让Vue根据注册表里存好的组件对象动态切换渲染目标,v-bind="widget.props"把配置面板里调好的参数一次性传给组件。以后新增组件,只需要在注册表加一条配置,渲染器零改动。
3. 目录结构拆解:源码文件太多看不出重点?我帮你标好地图
3.1 顶层目录总览
├── src/ │ ├── components/ # 可复用UI组件(非业务组件) │ ├── widgets/ # 可视化组件库(图表、文本、素材等) │ │ ├── charts/ # ECharts封装图表组件 │ │ ├── borders/ # 大屏常用边框装饰 │ │ └── commons/ # 文本框、图片、时间等基础组件 │ ├── views/ # 页面级组件 │ │ ├── editor/ # 搭建编辑器主页面 │ │ ├── preview/ # 全屏预览页面 │ │ └── dashboard/ # 已发布大屏列表页 │ ├── core/ # 核心逻辑(注册表、指令、渲染器) │ ├── stores/ # Pinia状态管理 │ ├── router/ # 路由配置 │ ├── api/ # 接口请求封装 │ └── utils/ # 工具函数 ├── public/ │ └── mock/ # 本地mock数据,跑起来不用等后端 └── vite.config.js # Vite配置(含别名、代理)这里建议重点关注三个目录:widgets是扩展点,新增可视化组件就放这里;core是运行机制,想搞懂拖拽和渲染原理就看这里;stores是数据流枢纽,改配置保存、画布交互都看这里。我第一次看源码时先扫了stores,因为状态管理是连接所有模块的地基——组件拖进来改什么、属性面板读什么、预览渲染什么,全在store里流转,把这个流理清了整个系统就通了一半。
3.2 widgets目录:组件分类的玄机
widgets目录按大类分了三块:charts是图表类,封装了柱状图、折线图、饼图、地图等ECharts常用图表。borders是大屏必不可少的边框装饰——大屏不套几个发光边框,总感觉少了点气质。commons是基础件,比如文本框、图片、时间组件。每个组件文件夹内部结构一致:
src/widgets/charts/barChart/ ├── index.js # 组件注册入口,声明名称、分类、默认尺寸 ├── BarChart.vue # 组件本体,负责渲染 └── schema.js # 属性配置schema,决定属性面板长什么样这种一个组件一个文件夹的“自包含”结构,好处是组件之间零耦合,复制一个文件夹再改改核心逻辑,就能快速产出一个新组件。想新增一个雷达图,只需要复制barChart这个目录,改组件名、改渲染逻辑、改schema配置,然后到入口文件里注册一下即可。
3.3 构建配置与Mock数据
vite.config.js配置了路径别名(@指向src),开发环境的代理转发到后端的/api路径。但源码本身带了一套本地Mock方案:在public/mock目录下提供了一些静态JSON文件,模拟大屏列表、组件分类等接口数据。这意味着前端在完全没有后端的情况下也能跑起来看效果,对快速上手来说非常友好。
// src/api/dashboard.js 接口请求与Mock切换逻辑 import axios from 'axios' // 判断运行环境,开发环境优先走mock数据 const isMock = import.meta.env.DEV && import.meta.env.VITE_USE_MOCK === 'true' export async function fetchDashboardList() { if (isMock) { // 直接请求public/mock下的静态文件,避免跨域和等待后端 const response = await axios.get('/mock/dashboard-list.json') return response.data } const response = await axios.get('/api/dashboard/list') return response.data }import.meta.env是Vite注入的环境变量,VITE_USE_MOCK在.env.development里配置。不想用Mock时,把环境变量改成false或者删掉对应的.env文件即可,这时候所有请求就会走后端代理。这种双通道设计的思路值得借鉴:前端的接口联调环境,加入Mock开关,遇到后端接口不完善的情况,整个前端组不至于原地阻塞。
4. 核心模块拆解:拖拽、属性绑定与数据联动
4.1 拖拽指令的实现细节
这套系统的拖拽没有引入vue-draggable-resizable之类的重量级库,而是基于Vue自定义指令加原生鼠标事件自己实现了一套。这样既控制了体积,也方便以后按需求扩展特殊交互。
// core/directives/drag.js 拖拽指令完整实现 import { useCanvasStore } from '../../stores/canvasStore' export const dragDirective = { mounted(el, binding) { const store = useCanvasStore() // 只有处于“编辑模式”时才允许拖拽,预览模式下拖拽指令不生效 if (!binding.value?.editable) return const widgetId = binding.value.widgetId let startX = 0 let startY = 0 let startPos = null const handleMouseDown = (e) => { // 只响应左键,避免中键和右键误触 if (e.button !== 0) return e.stopPropagation() // 记录按下瞬间的起点坐标,这是计算位移的基础 startX = e.clientX startY = e.clientY const widget = store.widgets.find(item => item.id === widgetId) startPos = { ...widget.position } store.activeId = widgetId // 标记拖拽状态,渲染器读取这个字段显示半透明效果 store.updateWidget(widgetId, { dragging: true }) document.addEventListener('mousemove', handleMouseMove) document.addEventListener('mouseup', handleMouseUp) } const handleMouseMove = (e) => { const dx = e.clientX - startX const dy = e.clientY - startY // 用起点加位移计算出新位置,而不是直接赋值鼠标坐标 store.updateWidget(widgetId, { position: { x: startPos.x + dx, y: startPos.y + dy } }) } const handleMouseUp = () => { store.updateWidget(widgetId, { dragging: false }) document.removeEventListener('mousemove', handleMouseMove) document.removeEventListener('mouseup', handleMouseUp) } el.addEventListener('mousedown', handleMouseDown) // 指令卸载时要清理事件,这是前端老生常谈的内存泄漏问题 el._cleanupDrag = () => { el.removeEventListener('mousedown', handleMouseDown) document.removeEventListener('mousemove', handleMouseMove) document.removeEventListener('mouseup', handleMouseUp) } }, unmounted(el) { if (typeof el._cleanupDrag === 'function') { el._cleanupDrag() } } }我在代码里加了几个容易忽略的关键处理:startPos存的是按下瞬间的坐标,计算位移时用它加偏移量,而不是直接用鼠标当前坐标。这样设计的原因是:如果直接赋坐标为位置值,鼠标按下瞬间组件会跳一下——因为鼠标在组件内部的偏移量没被减掉,这就是“组件漂移”现象。还有拖拽结束后要销毁document上的全局监听,不然会出现放开鼠标后组件还在跟随鼠标移动的灵异事件。editable通过binding.value传入,让同一个组件在预览模式不响应拖拽,这套指令写一次,编辑与预览两种场景通吃。
4.2 属性配置文件:属性面板是自动生成的
schema.js是配置面板的数据源,它描述了这个组件有哪些属性、用什么控件编辑、取值区间是什么。属性面板会遍历schema,自动生成对应的表单控件。这比每个组件手写一套面板配置要省非常多的工作量。
// src/widgets/charts/barChart/schema.js 柱状图的属性配置schema export default { // 基础属性 title: { label: '标题', type: 'input', default: '柱状图' }, // 图表数据 data: { label: '数据', type: 'data-editor', default: { xAxis: ['一月', '二月', '三月', '四月'], series: [ { name: '销售额', data: [120, 200, 150, 280] } ] } }, // 图表配色,支持颜色数组配置 colors: { label: '配色', type: 'color-picker', default: ['#36A0FF', '#36D6C9'] }, // 网格线是否显示,这是大屏设计中常被忽略的细节 showGrid: { label: '显示网格线', type: 'switch', default: false } }每个字段包含label(面板上显示的中文名)、type(控件类型,决定用什么表单组件编辑)、default(默认值)。属性面板读取这份schema后,自动v-for生成表单。新增组件时,只要把schema写好,属性面板的工作就自动完成了。这种配置驱动UI的模式,是搭建系统保持可扩展性的关键。要有“组件加一个,面板自动长出一个新区域”的效果,就要把schema设计成纯声明式。如果你的组件新增了一个业务属性,只需在schema里加一行,属性面板无需改动就多出一个控件。
4.3 数据联动:大屏的“活数据”是怎么接上去的
大屏搭建完只是个壳,接上真实数据才能用起来。这套系统把数据源类型设计成三种:静态数据(写死的JSON,适合Demo演示)、接口请求(后端通过URL返回数据)、事件联动(接收其他组件发布的数据)。后两种的实现思路:
// widgets/charts/barChart/BarChart.vue 图表组件的数据接收逻辑 <template> <div ref="chartEl" class="bar-chart"></div> </template> <script setup> import * as echarts from 'echarts' import { onMounted, onBeforeUnmount, watch, ref } from 'vue' const props = defineProps({ data: { type: Object, required: true }, colors: { type: Array, default: () => ['#36A0FF'] }, showGrid: { type: Boolean, default: false } }) const chartEl = ref(null) let chartInstance = null function renderChart() { if (!chartInstance) return const option = { color: props.colors, grid: { show: props.showGrid }, xAxis: { data: props.data.xAxis }, yAxis: {}, series: [{ type: 'bar', data: props.data.series[0].data, // 柱状图加圆角,视觉上更柔和,适合大屏的展示场景 itemStyle: { borderRadius: [4, 4, 0, 0] } }] } chartInstance.setOption(option) } // 监听data和配置变化,数据一变图表自动刷新 watch(() => props.data, renderChart, { deep: true }) watch(() => props.colors, renderChart, { deep: true }) onMounted(() => { chartInstance = echarts.init(chartEl.value) renderChart() // 监听画布容器尺寸变化,自动resize图表,避免大屏自适应时图表变形 window.addEventListener('resize', handleResize) }) // 使用ResizeObserver监听容器尺寸变化,防抖处理避免频繁触发 const handleResize = debounce(() => { chartInstance?.resize() }, 200) </script>组件接数据的方式简单直接:所有数据都通过props传入,组件内部watch数据变化然后调用setOption重新渲染。deep: true是必要的,因为数据是多层嵌套的JSON对象,不深度监听到不了属性变化。这套设计格的本质是让图表组件变成“纯展示组件”——它不关心数据是哪里来的,只关心props传递的内容对不对。数据来源在外部通过数据源管理器配置,用Pinia里面存储接口URL、请求方法,再通过组件props传入。这样图表组件保持纯粹,复用性高;数据源配置是另一个独立模块,互相不污染。
4.4 事件总线与大屏内部交互
大屏通常是单页展示,组件间的通信需求不会太复杂,这套系统使用了一个简化版事件总线来完成组件间联动。比如一个筛选器组件改变值后,需要通知多个图表组件更新数据。看看事件总线的核心代码:
// utils/eventBus.js 轻量级事件总线 class EventBus { constructor() { this.events = new Map() } // 监听事件 on(eventName, callback) { if (!this.events.has(eventName)) { this.events.set(eventName, []) } this.events.get(eventName).push(callback) redirect } // 触发事件 emit(eventName, payload) { if (!this.events.has(eventName)) return this.events.get(eventName).forEach(cb => cb(payload)) } // 解绑事件 off(eventName, callback) { if (!this.events.has(eventName)) return const callbacks = this.events.get(eventName).filter(cb => cb !== callback) this.events.set(eventName, callbacks) } } export default new EventBus()使用场景举例:时间筛选器组件在某年某月变化时,emit('date-change', selectedDate);柱状图组件在onMounted时,on('date-change', params => { ... }),然后根据params重新请求接口或本地过滤数据。这个事件总线的设计比直接通过Vuex或Pinia跨组件访问要轻量得多,而且与业务逻辑解耦。使用起来简便不代表可以滥用。如果页面上超过几十个组件有交互,事件名称管理一乱就难以追踪——那时候就需要升级为消息路由或者统一事件订阅了。
5. 避坑与排查指南:跑这个项目时最容易踩的坑
5.1 组件拖进画布后“闪一下”就不见了
现象:从左侧组件库拖入一个图表,画布上闪一下,组件列表里却有记录,但画布上找不到它。
原因:这个坑我一开始也踩了。渲染器的定位上下文不对——画布容器没设置position: relative,组件以absolute定位时是相对于整个页面而非画布区域进行定位的,可能被挪到屏幕外面去了。检查chrome devtools里组件DOM,它的left、top值实际上超出了画布中心位置不少像素。
解决:给画布容器补上position: relative,同时确认画布wrap层的宽高与store里的canvasSize一致。在容器样式上提前设置好相对定位,新拖入的组件才能在画布范围内正确展示。
5.2 ECharts图表不显示,控制台报“dom not created”
现象:echarts.init()执行时控制台报错:“dom not created”或初始化失败,图表区域空白。
原因:渲染组件时,ref="chartEl"对应的DOM还没挂载完成就立刻去init()了,常见于组件刚拖入画布、渲染器还没来得及完成DOM渲染时。还有一个场景是组件在动态渲染过程中,同一个DOM节点被创建又被销毁导致引用丢失。
解决:不要依赖mounted的单次初始化,把nextTick加进去。
import { nextTick } from 'vue' onMounted(async () => { await nextTick() // 确保DOM已经写入文档,并且ref可用 if (chartEl.value) { chartInstance = echarts.init(chartEl.value) renderChart() } })另外,组件如果被拖拽改变大小,需要在watch size变化时手动调用chartInstance.resize(),否则图表会比组件容器小一圈——这个属于ECharts的经典问题,不少人都踩过。
5.3 Mock数据在开发环境可用,但打包后404
现象:本地npm run dev一切正常,npm run build后部署到服务器,大屏列表接口请求报404,所有数据都加载不出来。
原因:Mock数据放在public/mock目录,npm run build时它会被复制到dist/mock,但请求路径仍是/mock/dashboard-list.json。部署时要看服务器是否把/mock路径路由到静态资源目录。用Nginx部署时,需要确认location /mock的alias边距指向正确;用Node部署时,静态资源中间件没有开放/mock目录,请求打到后端路由就404了。
解决:最省事的办法,打包前将Mock开关VITE_USE_MOCK置为false,让前端切口切换到真实后端接口。如果你的场景就是前端独立部署,必须在Nginx里配置:
location /mock/ { alias /usr/share/nginx/html/mock/; try_files $uri $uri/ =404; }这段配置的作用是让/mock开头的请求直接读取html目录下的静态JSON文件,不再走后端。我在本地验证过多次,这个坑几乎是所有带Mock数据的前端项目部署时的标配问题。
5.4 拖拽时列表数据变化但组件不跟随
现象:拖动组件时,属性面板里组件位置数值在变化,但画布上的组件纹丝不动。
原因:渲染器没有正确响应position对象的变化。看定位样式,它是一个计算属性,如果只监听props.widget.position.x这一个字段,不会重新计算这个计算属性。
解决:把widgetStyle改成多个直接依赖,或对widget整体做深度watch:
// 改为对整个widget深度监听 watch(() => props.widget, () => { /* 重新计算样式 */ }, { deep: true })计算属性内部依赖了widget.position.x、widget.position.y、widget.size.width等多个嵌套字段,要让依赖是响应式的并且能准确触发更新,在读取时不要给整个对象做解构操作,直接逐字段引用(props.widget.position.x)。这样Vue才能跟踪具体依赖。当时改完几处就正常了。这个问题的本质还是对Composition API响应式依赖收集的理解不够深——不算难,但排查时容易绕圈子。
5.5 大屏1920x1080设计稿,在不同分辨率的显示器上显示比例不对
现象:本地27寸显示器上完美铺满整屏,到客户的投影仪或大屏电视上一看,两边留黑边或者被裁切。
原因:大屏设计以1920x1080为基准,但很多客户的可视化大屏物理分辨率是3840x2160或1366x768,直接跑1920x1080的页面肯定会出现比例不一致的情况。搭建系统的预览页面没有做自适应缩放处理。
解决:基于设计稿尺寸做scale缩放,这是大屏搭建系统通行的做法。在预览页获取当前窗口大小,计算与1920x1080的缩放比,然后对整个大屏容器执行transform: scale(),用transform-origin: top left对齐:
const scaleX = window.innerWidth / 1920 const scaleY = window.innerHeight / 1080 const scale = Math.min(scaleX, scaleY) // 取最小比例保证完整显示 container.style.transform = `scale(${scale})` container.style.transformOrigin = 'top left'用min而不是分别拉伸铺满,是为了避免宽高比例不一致时图表被压扁变形。如果客户要求必须铺满不留边,可以用scaleX和scaleY分别拉伸;但这样做图表会被拉伸,视觉上会有变形,需要用CSS去修正。从我经历过的项目来看,优先保持等比缩放,再让客户确认背景色,效果更可控。
6. 二次开发进阶:注册一个自定义组件与改造建议
这套系统的扩展方式比预想的要简单——所有东西都围绕组件注册表运转。给读者演示一个完整流程:新做一个雷达图组件,从零到出现在画布上。
第一步,在src/widgets/charts/下新建目录radarChart,创建RadarChart.vue,核心代码就是渲染一个雷达图:
<!-- src/widgets/charts/radarChart/RadarChart.vue --> <template> <div ref="chartEl" class="radar-chart"></div> </template> <script setup> import * as echarts from 'echarts' import { onMounted, onBeforeUnmount, watch, ref } from 'vue' const props = defineProps({ indicator: { type: Array, default: () => [] }, data: { type: Array, default: () => [] } }) const chartEl = ref(null) let chartInstance = null function renderChart() { if (!chartInstance) return chartInstance.setOption({ radar: { indicator: props.indicator }, series: [{ type: 'radar', data: props.data }] }) } onMounted(async () => { await nextTick() if (chartEl.value) { chartInstance = echarts.init(chartEl.value) renderChart() } }) watch(() => [props.indicator, props.data], renderChart, { deep: true }) onBeforeUnmount(() => { chartInstance?.dispose() }) </script>第二步,写这个组件的schema:
// src/widgets/charts/radarChart/schema.js export default { indicator: { label: '指标维度', type: 'data-editor', default: [ { name: '质量', max: 100 }, { name: '速度', max: 100 }, { name: '成本', max: 100 } ] }, data: { label: '数据', type: 'data-editor', default: [ { name: '当前项目', value: [80, 70, 90] } ] } }第三步,创建index.js注册入口:
// src/widgets/charts/radarChart/index.js import RadarChart from './RadarChart.vue' import schema from './schema' export default { // 组件类型标识,必须全局唯一,相当于组件的身份证 name: 'radar-chart', // 组件库里显示的名称 title: '雷达图', // 分类信息,决定出现在组件库的哪个分组 category: 'charts', // 图标,组件库列表里的小图标,用来视觉查找 icon: 'radar-chart-icon', // 组件本体 component: RadarChart, // 属性配置schema schema }第四步,在组件注册入口文件里导入并注册:
// src/widgets/register.js import { registerWidget } from '../core/registry' import barChart from './charts/barChart' import radarChart from './charts/radarChart' registerWidget(barChart) registerWidget(radarChart)刷新页面,左侧组件库多一个雷达图,拖入画布即可配置属性并显示渲染结果。整个过程四步走,不涉及渲染器改动,也不碰属性面板,只改新增组件自己的目录就行。组件之间互不干扰,整个流程非常顺畅。这套机制的可维护性在于:组件边界清晰,每个组件自包含入口、渲染逻辑和属性描述三件套。
关于再往深了改造的几个方向:一是数据源增强。当前三种数据源够用,但如果你想把大屏做成SaaS产品,需要增加“SQL查询”或“API配置向导”,在数据源manager里扩展一个类型即可。二是模板与主题。这套系统如果没有大屏模板市场,每次搭建都从空白开始效率会比较低。可以设计一套模板JSON的结构(组件列表、布局、数据配置),增加“一键套用”功能,后面想做搭建系统的规模化交付,这个能力是分水岭。三是权限和发布流程,从简单开始做,给大屏加“版本号”和“发布状态”字段,做一个简单的审批流程,对客户侧的交付管理非常实用。
最后,聊一点前面踩过的坑。当时最困扰我的问题就是大屏适配的缩放策略,那时还没形成系统的适配方案,每个项目都临时处理,后来我在改造中强制把基准尺寸、缩放逻辑做成公共service,再遇到类似需求直接复用。从那以后,我每做一个大屏相关的项目,都强制走一遍这套流程:先明确基准分辨率,再定缩放策略,最后才动手写页面。这套系统的价值也正在这里——它不仅是工具,还能帮团队把大屏项目的技术规范固化下来。希望帮到你。
本文还有配套的精品资源,点击获取