可视化大屏系列(1):基于 Vue3 + ECharts 打造智慧工地大屏:项目初始化与响应式布局方案
文章目录:
- 一、引言
- 二、项目初始化
- 三、多分辨率适配与响应式布局
- 四、项目结构与目录规范
- 五、ECharts 与 Vue3 集成
- 六、数据动态更新与展示优化
- 七、UI 样式与主题定制
- 八、最佳实践与注意事项
- 九、总结与展望
一、引言
在智慧工地管理系统中,大屏展示作为一个非常重要的功能模块,能够将工地实时的生产数据、设备状况、人员情况等信息直观地呈现给管理者。为了确保在不同设备上的流畅展示,选择合适的框架与技术栈至关重要。本文将通过基于 Vue3 + ECharts 的方案,详细讲解如何搭建一个可适配多分辨率、具有响应式设计的智慧工地大屏。
二、项目初始化
2.1 创建 Vue3 项目
在终端运行以下命令来初始化一个 Vue3 项目:
# 使用 Vue CLI 创建 Vue3 项目
vue create smart-site-dashboard
# 选择 Vue 3.x 版本
2.2 安装 ECharts
通过 npm 安装 ECharts 以便在项目中使用:
npm install echarts --save
2.3 安装其他必需的插件
安装 vue-router
用于页面路由管理,安装 pinia
用于状态管理:
npm install vue-router pinia --save
2.4 配置项目结构
确保目录结构清晰、模块化,方便管理与扩展:
src/
assets/ # 静态资源,如图片、图标
components/ # 公共组件
views/ # 页面组件
store/ # Pinia 状态管理
router/ # 路由管理
utils/ # 工具函数,如 API 请求
assets/styles # 样式文件
三、多分辨率适配与响应式布局
3.1 响应式设计原则
响应式设计(Responsive Design)是一种使网页能够适应各种屏幕尺寸和设备类型的设计方法。通过灵活的布局和适配策略,响应式设计确保了网页在不同设备上都能拥有良好的用户体验。
响应式设计的基本原则:
- 流式布局:元素的宽度和位置不应使用固定值,而应基于相对比例(如百分比、vw、vh)来设置,使其能够根据屏幕宽度自动调整。
- 弹性盒子(Flexbox):通过 Flexbox 布局模型,能够轻松实现自适应的排版,保证在不同屏幕尺寸下内容能自适应地布局。
- CSS 媒体查询(Media Queries):通过媒体查询来根据不同设备的特性(如屏幕宽度、分辨率)来应用不同的样式。
3.2 使用 Element Plus 实现响应式布局
Element Plus
是一款基于 Vue 3 的 UI 框架,提供了强大的响应式布局支持。通过使用 el-row
和 el-col
组件,您可以灵活地实现各种屏幕尺寸下的响应式布局。以下是一个使用 Element Plus
的栅格系统来创建响应式布局的示例。
示例:
<template>
<el-row :gutter="20">
<el-col :span="12" :xs="24" :sm="12" :md="8">
<el-card>
<div class="card-content">内容 1</div>
</el-card>
</el-col>
<el-col :span="12" :xs="24" :sm="12" :md="8">
<el-card>
<div class="card-content">内容 2</div>
</el-card>
</el-col>
</el-row>
</template>
<script>
export default {
name: "ResponsiveLayout"
};
</script>
<style scoped>
.card-content {
padding: 20px;
background-color: #f4f4f4;
border-radius: 10px;
}
</style>
解释:
el-row
用于创建行,gutter
控制列之间的间距。el-col
用于定义列宽,可以通过xs
、sm
、md
等属性来设置不同屏幕尺寸下的列宽。例如,在屏幕宽度小于 768px 时,xs="24"
表示每列占满 100% 宽度;在屏幕宽度大于 768px 时,sm="12"
表示每列占 50% 宽度;在屏幕宽度大于 1200px 时,md="8"
表示每列占 33.33% 宽度。
这种方法能够在不同设备上自适应地调整列宽,使得布局在各种屏幕尺寸下都能保持合理的显示效果。
3.3 配置 TailwindCSS 实现响应式设计
TailwindCSS 是一个功能强大的 CSS 框架,提供了一套高度可配置的工具类,用于构建响应式布局。它通过内置的断点类,使得开发者能够轻松为不同设备尺寸编写样式。
安装 TailwindCSS
首先,您需要安装 TailwindCSS,并配置好它的基本文件:
npm install tailwindcss postcss autoprefixer --save-dev
npx tailwindcss init
然后,在 tailwind.config.js
中配置内容:
module.exports = {
content: ['./src/**/*.{html,vue,js}'],
theme: {
extend: {},
},
plugins: [],
}
在 src/assets/styles/index.css
中引入 TailwindCSS 的基础样式:
@tailwind base;
@tailwind components;
@tailwind utilities;
示例:
<template>
<div class="container mx-auto px-4">
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
<div class="bg-gray-200 p-4 rounded-lg">内容 1</div>
<div class="bg-gray-200 p-4 rounded-lg">内容 2</div>
<div class="bg-gray-200 p-4 rounded-lg">内容 3</div>
</div>
</div>
</template>
<script>
export default {
name: 'ResponsiveGridLayout',
};
</script>
<style scoped>
/* Additional styles for this component */
</style>
解释:
grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3
:使用 Tailwind 的网格系统。默认情况下,所有列占据 1 列(grid-cols-1
);当屏幕尺寸大于sm
时,使用 2 列(sm:grid-cols-2
);当屏幕尺寸大于md
时,使用 3 列(md:grid-cols-3
)。gap-4
:设置网格项之间的间距为 4(单位是 0.25rem)。p-4
:为每个网格项添加 4 的内边距。rounded-lg
:设置网格项的圆角。
3.4 使用 CSS 媒体查询实现分辨率适配
除了使用框架和工具类之外,您还可以通过 CSS 的 @media
媒体查询来手动处理不同屏幕尺寸下的样式调整。通过媒体查询,您可以为不同分辨率的设备指定不同的样式规则。
示例:
/* 默认样式 */
.container {
padding: 20px;
width: 100%;
}
/* 屏幕宽度大于 600px 时 */
@media (min-width: 600px) {
.container {
padding: 30px;
width: 80%;
}
}
/* 屏幕宽度大于 1200px 时 */
@media (min-width: 1200px) {
.container {
padding: 40px;
width: 60%;
}
}
解释:
- 在默认情况下,
.container
使用 20px 的内边距和 100% 的宽度。 - 当屏幕宽度大于 600px 时,
.container
的宽度调整为 80%,内边距增加到 30px。 - 当屏幕宽度大于 1200px 时,
.container
的宽度调整为 60%,内边距为 40px。
3.5 使用视口单位(vw/vh)进行适配
除了传统的响应式布局方法,使用 视口单位(vw
和 vh
)也是一种有效的适配方法。视口单位可以根据视口的宽度(vw
)或高度(vh
)动态调整元素的尺寸。
示例:
.container {
width: 80vw; /* 宽度为视口宽度的 80% */
height: 40vh; /* 高度为视口高度的 40% */
font-size: 4vw; /* 字体大小为视口宽度的 4% */
}
这种方法适合用于布局和字体大小调整,尤其是在大屏幕设备上。使用 vw
和 vh
可以确保元素根据屏幕大小自适应。
四、项目结构与目录规范
为确保项目的可维护性与扩展性,我们采用了以下的目录结构:
src/
assets/ # 存放所有静态资源,如图片、字体、图标
components/ # 存放公共组件(如数据卡片、按钮、表单控件)
views/ # 存放各个页面视图(如首页、大屏显示页)
store/ # 存放 Pinia 状态管理模块
router/ # 路由管理
utils/ # 存放工具函数和 API 封装
assets/styles/ # 存放 SCSS 或 TailwindCSS 配置文件
五、ECharts 与 Vue3 集成,使组件更便捷
5.1 ECharts 与 Vue3 集成的基础
ECharts 提供了丰富的图表类型,如折线图、柱状图、饼图等,适合各种数据展示需求。为了让 ECharts 与 Vue3 的集成更加高效,我们首先需要安装 echarts
库。
安装 ECharts:
npm install echarts --save
然后,我们可以创建一个 Vue3 组件来封装图表的渲染过程,只通过传递数据、颜色、图表类型等简单参数,来快速生成图表。
5.2 封装 ECharts 组件
为了使图表组件更加灵活,我们将 ECharts 的初始化、更新和销毁封装到一个 Vue3 组件中。此时,我们的组件将接收 data
、color
、chartType
等参数,用户仅需传递数据、颜色和图表类型即可。
ECharts 组件封装:
<template>
<div ref="chartContainer" :style="{ width: width, height: height }"></div>
</template>
<script>
import { onMounted, onBeforeUnmount, ref, watch } from 'vue';
import * as echarts from 'echarts';
export default {
name: 'EChartsComponent',
props: {
data: {
type: Array,
required: true
},
chartType: {
type: String,
default: 'bar' // 默认柱状图
},
colors: {
type: Array,
default: () => ['#409EFF', '#67C23A'] // 默认颜色
},
width: {
type: String,
default: '100%'
},
height: {
type: String,
default: '400px'
}
},
setup(props) {
const chartContainer = ref(null);
let chartInstance = null;
const initChart = () => {
chartInstance = echarts.init(chartContainer.value);
const option = generateOption();
chartInstance.setOption(option);
};
const updateChart = () => {
if (chartInstance) {
const option = generateOption();
chartInstance.setOption(option);
}
};
// 根据传入的参数生成配置项
const generateOption = () => {
return {
tooltip: {},
xAxis: {
type: 'category',
data: props.data.map(item => item.name)
},
yAxis: {
type: 'value'
},
series: [
{
name: '数据',
type: props.chartType,
data: props.data.map(item => item.value),
itemStyle: {
color: (params) => {
return props.colors[params.dataIndex % props.colors.length];
}
}
}
]
};
};
onMounted(() => {
initChart();
});
onBeforeUnmount(() => {
if (chartInstance) {
chartInstance.dispose();
}
});
// Watch for props changes to update chart
watch(() => props.data, updateChart);
watch(() => props.chartType, updateChart);
watch(() => props.colors, updateChart);
return {
chartContainer
};
}
};
</script>
<style scoped>
/* 可添加一些自定义样式 */
</style>
5.3 使用封装的 ECharts 组件
通过封装 ECharts 组件后,我们可以在其他 Vue 组件中方便地引用它,只需要传递图表数据、颜色和图表类型等参数,无需关心图表的细节实现。
使用示例:
<template>
<div>
<h3>工地巡检统计</h3>
<EChartsComponent
:data="chartData"
chartType="bar"
:colors="['#ff7f50', '#87cefa']"
width="100%"
height="400px"
/>
</div>
</template>
<script>
import { ref } from 'vue';
import EChartsComponent from './components/EChartsComponent';
export default {
name: 'InspectionStats',
components: {
EChartsComponent
},
setup() {
const chartData = ref([
{ name: '任务1', value: 120 },
{ name: '任务2', value: 200 },
{ name: '任务3', value: 150 },
{ name: '任务4', value: 80 },
{ name: '任务5', value: 70 }
]);
return {
chartData
};
}
};
</script>
<style scoped>
/* 可添加一些自定义样式 */
</style>
解释:
EChartsComponent
被作为子组件引入,并传入了data
、chartType
、colors
等参数来定义图表的内容。data
参数包含了图表所需的数据,包括每个任务的名称和完成情况。chartType
用于选择图表类型,这里设置为bar
(柱状图)。colors
参数允许我们自定义图表的颜色,使得不同数据系列之间可以有不同的显示颜色。width
和height
控制图表的尺寸,您可以根据需要调整这些属性。
5.4 优化 ECharts 性能
随着数据量的增加,图表的渲染性能可能会受到影响。为了提高性能,我们可以采取以下优化措施:
- 懒加载图表数据:只有在用户需要查看图表时才加载数据,避免一次性加载大量数据。
- 数据虚拟化:使用虚拟化技术,仅渲染当前视口内的数据,减少页面渲染压力。
- 图表缓存:对于重复渲染的图表,缓存已渲染的内容,避免不必要的重新渲染。
六、数据动态更新与展示优化
6.1 数据动态更新的需求
通过以下两种方式来进行数据的动态更新:
- 定时轮询:定期请求后台接口获取最新的数据,适用于数据更新频率相对较低的场景。
- WebSocket:通过 WebSocket 协议与后台建立持久连接,实时接收后台推送的数据,适用于数据更新频率较高的场景。
6.2 定时轮询实现数据更新
定时轮询是最常见的数据更新方式,通常用于数据变化频率较低的场景。通过 setInterval
定时请求后台接口,获取最新数据并更新视图。
实现步骤:
- 创建定时任务:使用
setInterval
定期调用后台接口,获取最新数据。 - 更新数据:在获取到新的数据后,更新页面中显示的数据。
代码实现:
<template>
<div>
<h3>巡检任务完成情况</h3>
<EChartsComponent
:data="chartData"
chartType="bar"
:colors="['#ff7f50', '#87cefa']"
width="100%"
height="400px"
/>
</div>
</template>
<script>
import { ref, onMounted, onBeforeUnmount } from 'vue';
import { getRequest } from '@/utils/request';
import EChartsComponent from './components/EChartsComponent';
export default {
name: 'InspectionStats',
components: {
EChartsComponent
},
setup() {
const chartData = ref([]);
let intervalId = null;
// 获取巡检任务数据
const fetchInspectionData = async () => {
try {
const data = await getRequest('/inspection/task');
chartData.value = data.map(item => ({
name: item.taskName,
value: item.completedCount
}));
} catch (error) {
console.error('获取数据失败', error);
}
};
// 定时请求后台数据
const startPolling = () => {
fetchInspectionData(); // 获取一次初始数据
intervalId = setInterval(fetchInspectionData, 30000); // 每30秒请求一次数据
};
// 组件销毁时清除定时器
onBeforeUnmount(() => {
clearInterval(intervalId);
});
// 页面初始化时启动定时任务
onMounted(() => {
startPolling();
});
return {
chartData
};
}
};
</script>
<style scoped>
/* 可添加一些自定义样式 */
</style>
解释:
fetchInspectionData
函数是一个异步函数,调用后台接口/inspection/task
获取巡检任务的数据。- 使用
setInterval
每 30 秒自动请求一次数据,更新页面展示的图表数据。 clearInterval(intervalId)
确保组件销毁时清除定时器,避免内存泄漏。
这种方式适用于数据更新频率较低的场景,比如巡检任务的完成情况、设备的状态等。
6.3 使用 WebSocket 实现实时数据推送
通过 WebSocket,前端和后台建立了长连接,后台可以随时向前端推送更新的数据。
实现步骤:
- 与后台建立 WebSocket 连接:前端通过 WebSocket 协议与后台建立连接。
- 接收推送的数据:前端通过 WebSocket 监听数据变化,当数据发生变化时,立即更新界面。
- 处理断线重连:如果 WebSocket 连接中断,需要重新建立连接。
代码实现:
<template>
<div>
<h3>设备运行状态</h3>
<EChartsComponent
:data="chartData"
chartType="pie"
:colors="['#ff7f50', '#87cefa']"
width="100%"
height="400px"
/>
</div>
</template>
<script>
import { ref, onMounted, onBeforeUnmount } from 'vue';
import { showError } from '@/utils/request';
import EChartsComponent from './components/EChartsComponent';
export default {
name: 'DeviceStatus',
components: {
EChartsComponent
},
setup() {
const chartData = ref([]);
let ws = null;
// 连接 WebSocket 服务
const connectWebSocket = () => {
ws = new WebSocket('wss://example.com/device/status'); // 后台 WebSocket 地址
ws.onopen = () => {
console.log('WebSocket 连接成功');
};
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
chartData.value = [
{ name: '在线设备', value: data.online },
{ name: '离线设备', value: data.offline }
];
};
ws.onerror = (error) => {
showError('WebSocket 连接出错');
};
ws.onclose = () => {
console.log('WebSocket 连接关闭');
// 处理连接关闭后的逻辑,可能需要重连
};
};
// 页面加载时初始化 WebSocket
onMounted(() => {
connectWebSocket();
});
// 组件销毁时关闭 WebSocket 连接
onBeforeUnmount(() => {
if (ws) {
ws.close();
}
});
return {
chartData
};
}
};
</script>
<style scoped>
/* 可添加一些自定义样式 */
</style>
解释:
- 使用
WebSocket
创建一个长连接,与后台进行实时数据交换。 - 当后台发送新的设备状态数据时,
onmessage
事件会触发,更新图表中的数据。 ws.close()
在组件销毁时关闭 WebSocket 连接,防止内存泄漏。
WebSocket 适用于实时性要求较高的场景,比如设备状态监控、实时施工进度等。
6.4 展示优化与性能提升
随着数据量的增加,图表的渲染性能可能会受到影响。为了解决这个问题,我们可以采用以下优化策略:
- 数据节流与防抖:对于高频率的数据更新,采用节流和防抖机制,减少不必要的渲染和请求。
- 虚拟化图表:在数据量非常大的情况下,可以通过虚拟化技术,仅渲染当前视口内的图表数据。
- 图表缓存:对于重复渲染的图表,采用缓存机制,避免每次都重新渲染。
示例:图表更新防抖优化
import { debounce } from 'lodash';
// 防抖更新图表
const updateChartDebounced = debounce(updateChart, 300); // 延迟300ms更新
通过防抖机制,可以避免频繁的图表更新,减轻浏览器的渲染负担。
七、UI 样式与主题定制
7.1 UI 样式的基本原则
- 简洁易懂:界面应尽量简洁,避免冗余信息,保证用户可以快速找到所需操作。
- 一致性:不同模块和页面的样式设计要保持一致,确保整体视觉风格统一,避免混乱。
- 响应式设计:确保界面能够适配不同分辨率和设备,提供良好的移动端体验。
- 品牌元素:UI 样式应融入项目的品牌元素,如颜色、字体、图标等,使系统具有独特的视觉识别性。
7.2 使用 uView UI 组件库进行样式定制
uView UI 是 UniApp 中常用的UI组件库,它提供了大量常用的组件,如按钮、输入框、表单、弹窗、导航栏等,且支持丰富的定制选项。我们可以通过uView UI来快速构建符合需求的页面,同时利用其样式定制功能进行外观调整。
配置 uView UI 主题
uView UI 提供了内置的主题配置选项,我们可以通过修改 uView
的主题配置文件,调整全局样式,包括颜色、字体等。
-
安装 uView UI 组件库:
首先,确保已经在项目中安装了 uView UI,并在
main.js
中引入:import uView from 'uview-ui'; import { createApp } from 'vue'; const app = createApp(App); app.use(uView); app.mount('#app');
-
修改主题配置:
在
uView
中,可以通过修改uni.scss
文件,快速自定义样式。你可以自定义基本颜色、字体、间距等样式。示例:修改
uni.scss
主题配置:/* uni.scss */ $primary-color: #42b983; // 修改主色调为绿色 $font-size-base: 14px; // 设置默认字体大小 $font-family: "Helvetica Neue", "Arial", sans-serif; // 设置全局字体
通过这些配置,所有使用
$primary-color
和$font-size-base
的组件和样式都会自动生效,保持全局一致性。
组件样式定制
如果我们需要对某个组件的样式进行更精细的定制,可以使用 class
或 style
来覆盖组件的默认样式。uView UI 组件支持通过 custom-style
属性来定义自定义样式。
示例:自定义按钮样式:
<template>
<u-button
class="custom-btn"
type="primary"
:style="{ backgroundColor: btnColor, fontSize: fontSize }"
>
提交
</u-button>
</template>
<script setup>
import { ref } from 'vue';
const btnColor = ref('#42b983'); // 自定义按钮背景色
const fontSize = ref('16px'); // 自定义字体大小
</script>
<style scoped>
.custom-btn {
border-radius: 10px; /* 设置按钮圆角 */
padding: 12px 24px; /* 设置按钮内边距 */
}
</style>
7.3 主题定制与动态切换
通过改变全局 CSS 变量来实现这一功能。
动态切换主题
首先,定义两个主题的样式文件,如 light-theme.scss
和 dark-theme.scss
:
light-theme.scss:
/* 浅色主题 */
$primary-color: #42b983;
$background-color: #ffffff;
$text-color: #333333;
dark-theme.scss:
/* 深色主题 */
$primary-color: #2c3e50;
$background-color: #2c3e50;
$text-color: #ecf0f1;
在 Vue 组件中,可以通过动态切换主题样式来实现主题的变化。使用 useState
或 useStorage
保存用户选择的主题,并在页面加载时应用相应的主题样式。
示例:主题切换功能:
<template>
<div :class="theme">
<u-button @click="toggleTheme">切换主题</u-button>
<p>当前主题: {{ theme }}</p>
</div>
</template>
<script setup>
import { ref } from 'vue';
const theme = ref(localStorage.getItem('theme') || 'light'); // 读取用户主题设置
// 切换主题
const toggleTheme = () => {
theme.value = theme.value === 'light' ? 'dark' : 'light';
localStorage.setItem('theme', theme.value); // 保存用户选择
document.documentElement.className = theme.value; // 更新主题样式
};
</script>
<style scoped>
/* 配置浅色和深色模式的不同样式 */
.light {
--primary-color: #42b983;
--background-color: #ffffff;
--text-color: #333333;
}
.dark {
--primary-color: #2c3e50;
--background-color: #2c3e50;
--text-color: #ecf0f1;
}
/* 使用 CSS 变量来统一管理主题样式 */
body {
background-color: var(--background-color);
color: var(--text-color);
}
</style>
7.4 响应式布局与适配
为了更好地适配不同设备和分辨率,我们可以通过设置响应式栅格系统来实现内容的自适应布局。常见的响应式布局工具如 Flexbox
和 CSS Grid
都可以帮助我们实现这一目标。
示例:使用 Flexbox 实现响应式布局:
<template>
<div class="container">
<div class="item" v-for="(item, index) in items" :key="index">{{ item }}</div>
</div>
</template>
<script setup>
const items = ['设备状态', '巡检数据', '施工进度'];
</script>
<style scoped>
.container {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
gap: 20px;
}
.item {
flex: 1 1 200px;
background: #f0f0f0;
padding: 20px;
border-radius: 8px;
text-align: center;
}
@media (max-width: 768px) {
.item {
flex: 1 1 100%; /* 小屏幕时每个 item 占满一行 */
}
}
</style>
通过上述代码,我们使用 flex
布局来实现动态自适应。并通过媒体查询(@media
)来控制小屏幕设备上的显示效果。
八、总结与展望
本文介绍了如何通过 Vue3 + ECharts 实现一个智慧工地的实时数据展示大屏,并讲解了如何实现响应式布局,确保适配各种屏幕。通过封装 ECharts 为组件,提升了代码复用性和可维护性。接下来,我们可以进一步扩展功能,增加更多数据可视化形式,并优化性能,以满足项目的需求。