尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

基于Vue和JavaScript的AJ-report大屏驾驶舱设计源码解析

基于Vue和JavaScript的AJ-report大屏驾驶舱设计源码解析 简介这是基于Vue和JavaScript开发的AJ-report大屏驾驶舱设计源码。AJ-report是一套专注于报表设计与大屏展示的开源框架面向需要建设数据可视化大屏的前端与全栈开发者可用于业务报表、运营监控、指挥中心等场景适合有一定Vue和JavaScript基础、希望快速搭建驾驶舱页面的技术人员参考学习。压缩包共928个文件整体容量约63.9MB主体包括181个JavaScript文件、179个Java文件、171个Vue文件、209个PNG图片以及JSON、Markdown、XML、CSS、SCSS等辅助资源覆盖组件页面、报表逻辑、后端接口、配置样例与说明文档目录结构清晰前端页面与后端服务分离便于按模块阅读理解和二次开发。文件中还包含Dockerfile、SQL脚本和shell脚本可辅助本地环境搭建、数据库初始化与快速部署同时附带JAR包、字体及图标样式资源适合直接构建报表大屏原型并进一步扩展。目前已有581人浏览或学习可作为理解AJ-report框架、参考大屏布局设计、学习图表组件封装与数据绑定的基础源码。1. 大屏驾驶舱的切入点为什么是AJ-report Vue JavaScript第一次接到大屏驾驶舱的需求时如果只盯着“好看”两个字很容易把项目做成一张静态PPT。真正的大屏驾驶舱要解决两个问题数据从哪来以及视图怎么在固定分辨率下稳定呈现。AJ-report擅长把SQL查询变成可复用的报表数据集Vue和JavaScript则负责把数据变成可交互的屏幕组件。“基于Vue和JavaScript的AJ-report大屏驾驶舱设计源码”说的就是把这两层打通的一套前端骨架而不是某个现成的模板下载包。适合正在给AJ-report做二次开发、需要从零搭一个可投屏看板的工程师。下文会从数据模型讲起再到Vue组件结构和请求封装最后落到缩放与部署的细节。2. 先理清AJ-report大屏驾驶舱的设计模型与数据流2.1 从JSONScheme组件树到Vue动态组件AJ-report在保存大屏看板时会把画布上的组件位置、数据绑定和刷新时间写成一个JSON结构。前端拿到这个结构才能知道哪里放KPI、哪里放图表。一个典型的组件节点如下{ screenId: dash-01, refreshSec: 30, components: [ { id: kpi1, type: KPI, x: 1, y: 2, w: 6, h: 4, datasetId: 1001, enabled: true }, { id: line1, type: LineChart, x: 8, y: 2, w: 18, h: 10, datasetId: 1002, enabled: true } ] }每个组件都带有绝对坐标和宽高。x、y是左上角相对于画布的百分比坐标w、h是组件宽度和高度。前端把这份JSON传给一个ScreenRenderer通过v-for循环组件再根据type找到对应的Vue组件。这里的关键是不要给每个图表单独写路由而是写一套“组件注册表”import KpiCard from ./components/KpiCard.vue import TrendChart from ./components/TrendChart.vue const componentMap { KPI: KpiCard, LineChart: TrendChart, BarChart: TrendChart } export function resolveComponent(type) { return componentMap[type] || null }这样新增一种图表只需要注册新的Vue组件不影响驾驶舱主框架。对于已经是报表形式的数据AJ-report往往直接给出一个数组前端根据组件类型决定如何渲染。下表是常见组件类型与前端处理方式的对应关系。componentType前端输出数据要求说明KPI、Number指标卡数字单值或第一行第一列数值保留小数位与单位LineChart、BarChartECharts图表维度列加1到多列指标需要聚合为seriesScrollTable轮播表格多列结构化数据注意首列对齐和换行MapChart地图区域名称加指标值考虑行政名称归一化2.2 把AJ-report二维结果压成图表OptionAJ-report的大屏接口一般返回字段名和二维行数据结构像是{ fields: [date, zone, sales], rows: [ [2025-01-01, 华东, 2300], [2025-01-01, 华南, 1800] ] }其中fields描述列顺序rows是数据行。ECharts不直接接受这种结构前端要先把“宽表”展开成“维度数组 指标系列”。我一般会在utils/reportData.js里放一个统一转换函数/** 将AJ-report行列结果转换为分组对象维度取第一列其余视为指标 */ export function transformReport(fields, rows) { const dimIndex 0 const metrics fields.slice(1) const result { dimensions: [], series: [] } metrics.forEach(name { result.series.push({ name, data: [] }) }) rows.forEach(row { result.dimensions.push(String(row[dimIndex])) metrics.forEach((metric, idx) { const value Number(row[idx 1]) result.series[idx].data.push(isFinite(value) ? value : 0) }) }) return result }dimIndex用来指定哪一列是X轴维度调用方也可以把该参数提出来metrics会排除维度列之外的所有列适用于多指标折线图。处理空字符串时Number()会得到0但这可能掩盖真实空值因此用isFinite判断后再决定填入0还是null。如果业务上需要空值断开折线应当保留null而不是补零。2.3 用刷新周期裁剪请求量大屏驾驶舱上线后最常见的问题是十几个组件各自定时请求AJ-report后端被瞬时请求打满。解决思路是根据组件配置里的refreshSec分组而不是每个组件开一个setInterval。例如const refreshPool new Map() function registerRefresh(component) { const key component.refreshSec || 30 if (!refreshPool.has(key)) { refreshPool.set(key, []) } refreshPool.get(key).push(component) } function triggerGroup(seconds) { const group refreshPool.get(seconds) || [] group.forEach(component { loadComponentData(component) }) } setInterval(() triggerGroup(30), 30000) setInterval(() triggerGroup(60), 60000)这段代码把刷新时间相同的组件放在同一组里每组对应一个定时器。实际项目中还要加一个随机延迟让同一组的请求错峰发出避免整组同时打爆后端。数据返回后按component.id更新到响应式状态驾驶舱只关心“组件要不要重绘”不关心这一步是由哪个定时器触发的。3. 用Vue和JavaScript搭出大屏驾驶舱的骨架3.1 初始化项目并处理vue安装依赖和环境配置大屏端不推荐在现有业务项目里堆页面尽量独立初始化。采用Vite创建Vue3加JavaScript工程命令如下npm create vitelatest aj-screen -- --template vue cd aj-screen npm install echarts axios vue-router4 npm install --save-dev sass npm run dev--template vue生成的是JavaScript版工程不是TypeScript版如果团队后续需要类型约束再手动引入langts。这一步经常遇到的问题就是node版本太低导致npm install报ERESOLVE依赖树冲突建议把Node升到18及以上。HBuilderX用户可以用“打开目录”方式导入项目但它主要适合查看html/css/javascript文件编译运行仍在终端里执行。接着修改vite.config.js把接口前缀指向本地代理import { defineConfig } from vite import vue from vitejs/plugin-vue export default defineConfig({ base: ./, plugins: [vue()], server: { port: 5173, proxy: { /aj-report: { target: http://localhost:9095, changeOrigin: true } } } })base: ./是为了避免之后打包部署到Nginx子目录时JavaScript和CSS资源路径变成绝对路径而白屏。代理配置只影响本地开发上线时由Nginx或网关转发/aj-report。3.2 用设计器坐标生成栅格布局AJ-report的设计器输出的是坐标和宽高前端可以直接利用CSS的绝对定位来还原画布。大屏以1920乘1080为基准在根容器上固定这个尺寸再通过外层transform: scale做整体缩放。单个组件的位置这样计算template div classscreen-stage :style{ width: 1920px, height: 1080px } div v-forcomp in screen.components :keycomp.id classcell-position :stylecellStyle(comp) component :isresolveComponent(comp.type) :componentcomp / /div /div /template script setup function cellStyle(comp) { return { left: comp.x %, top: comp.y %, width: comp.w %, height: comp.h % } } /script style scoped .cell-position { position: absolute; transform: translate(-50%, -50%); } /style这里x、y的含义是组件中心点坐标所以加了一个translate(-50%, -50%)让组件居中。另一种常见做法是x、y为左上角坐标此时不需要这个translate。使用哪一种取决于AJ-report看板导出的语义拿到JSONScheme后先打印一个组件节点确认再决定是否保留该行。3.3 CSS变量与主题控制大屏视觉风格容易失控问题往往出在颜色散落在每个组件里。统一用CSS变量管理背景、主色、辅助色、文字颜色后续换肤不用改动业务组件:root { --screen-bg: #0b1a2e; --accent-blue: #00c6ff; --accent-orange: #ff9f43; --text-color: #f0f7ff; --font-scale: 1; } .screen-stage { background: radial-gradient(circle at 50% 0%, #10243d, var(--screen-bg)); color: var(--text-color); } .kpi-title { font-size: calc(16px * var(--font-scale)); }--font-scale可以先写成1等缩放计算出来后由JavaScript动态修改这样设计稿内的字号能随屏幕大小按比例变化。相比在组件里硬编码#FFFFFF、#FF0000变量方式能减少后期调整成本。下面的表总结了初始化阶段需要关注的几个命令点。操作命令容易踩的坑创建Vue工程npm create vite目录名不能带中文安装依赖npm installNode版本过低报ERESOLVE本地起服务npm run dev端口被占用或代理未生效打包npm run build资源路径未设置base4. 在组件内请求AJ-report数据并驱动图表4.1 封装报告请求模块处理鉴权与统一前缀大屏工程里不要每个组件各自调fetch更不要在每个组件里写AJ-report地址。先统一建一个src/api/report.js模块import axios from axios const request axios.create({ baseURL: import.meta.env.VITE_REPORT_API || /aj-report, timeout: 10000 }) request.interceptors.request.use(config { const token localStorage.getItem(report_token) if (token) { config.headers[X-Report-Token] token } return config }) export function fetchReport(datasetId) { return request.get(/data/${datasetId}).then(res res.data) }baseURL从环境变量读取没有配置时默认走Vite代理。X-Report-Token是自定义请求头实际鉴权字段名需要和AJ-report后端约定有的环境需要Authorization: Bearer token。不要把这个token写进源码登录成功后写入localStorage或内存状态避免大屏源码泄露后接口被随意调用。timeout建议设在8000到15000毫秒大屏投放在弱网下很常见设太短会导致刷新失败频繁。4.2 用通用Chart组件驱动ECharts大屏驾驶舱里会有多个图表组件但它们的生命周期高度相似挂载时初始化、属性变化时更新、卸载时销毁。我一般会抽一个BaseChart.vuescript setup import * as echarts from echarts import { ref, onMounted, onBeforeUnmount, watch } from vue const props defineProps({ option: { type: Object, required: true } }) const el ref(null) let chart null onMounted(() { chart echarts.init(el.value) chart.setOption(props.option) }) watch(() props.option, opt { chart.setOption(opt, true) }, { deep: true }) onBeforeUnmount(() { if (chart) { chart.dispose() } }) /script template div refel classbase-chart/div /template style scoped .base-chart { width: 100%; height: 100%; } /stylesetOption(opt, true)的第二个参数表示清空之前的series再重绘适合大屏切换数据集或刷新后结构变化明显的场景。如果希望动画平滑过渡改成chart.setOption(opt)即可。这里要留意watch的deep: true会对整个option做深比较数据量大时有一定开销驾驶舱组件数量少于20个时没有问题。4.3 通过vue路由参数加载屏幕并过滤启用组件大屏通常通过URL区分不同屏幕例如https://host/screen?screenIddash-01。用Vue Router的useRoute拿到参数再结合JavaScript的filter函数只加载启用状态的组件。import { useRoute } from vue-router import { ref } from vue const route useRoute() const screenId route.query.screenId || default const screenModules ref([]) async function loadScreen() { const screen await getScreenJSON(screenId) const components screen.components.filter(item item.enabled) const requests components.map(item fetchReport(item.datasetId)) const datasets await Promise.all(requests) screenModules.value components.map((component, index) ({ ...component, dataset: datasets[index] })) }这里的filter函数是JavaScript数组常用的筛选方法比for...of加if的写法更直观。路由参数不能直接信任如果驾驶舱可能被外部URL访问screenId需要做白名单或签名校验否则别人可以遍历ID看到其他看板。组件里没有用props传递路由参数而是使用时再读取原因是同一屏幕的多个组件都需要知道当前screenId放在一个可注入的store中更合理。5. 大屏驾驶舱上线前的一个必做细节用基准分辨率做动态缩放与打包验证5.1 基于参考分辨率计算缩放比例大屏不会被用户拖着窗口乱玩但投屏的显示器可能不是1920乘1080。常见做法是固定设计稿基准然后按比例整体缩放这样页面不会出现横向滚动条。封装一个useScreenScale.js组合式函数import { ref, onMounted, onBeforeUnmount } from vue const designWidth 1920 const designHeight 1080 const scale ref(1) function updateScale() { const current Math.min( window.innerWidth / designWidth, window.innerHeight / designHeight ) scale.value Number(current.toFixed(4)) } let timer null function debouncedUpdate() { clearTimeout(timer) timer setTimeout(updateScale, 150) } export function useScreenScale() { onMounted(() { updateScale() window.addEventListener(resize, debouncedUpdate) }) onBeforeUnmount(() { window.removeEventListener(resize, debouncedUpdate) }) return { scale } }Math.min保证高度或宽度哪一边先不够就按哪一边缩。debouncedUpdate的150毫秒延迟是为了防止窗口连续变化时反复计算布局。缩放后根容器固定为设计稿尺寸并把transform: scale(scale)挂在容器上同时设置transform-origin: top left如果忘记这个属性缩放中心会在页面中间左上角会露出黑边。5.2 打包部署后的布局自检打包后在本地静态服务器预览不要直接双击index.html。常见问题是资源404导致Vue没挂载画面空白或布局错乱。打开浏览器控制台的Network面板看JavaScript文件路径是否带着assets/xxx.js。如果路径不对优先确认vite.config.js里的base: ./如果用router的history模式还需要让Nginx将所有非文件请求回退到index.html。验证缩放是否正常的办法很简单把浏览器切换到1366乘768的分辨率检查页面没有横向滚动条再看控制台里scale值是否约等于0.71投屏到目标显示器后再按实际分辨率检查一次四角是否完整显示。这个检查应该放进发布流程否则换了一台显示器就要重调布局。本文还有配套的精品资源点击获取
返回列表