Vue 3 + JavaScript 构建地质灾害预警系统:从地图渲染到预警闭环 简介基于Vue与JavaScript实现的地质灾害预警系统源码及项目文档主要面向毕业设计、课程设计与项目开发等场景覆盖前端组件化开发、数据可视化、地图展示和预警信息处理等环节整体定位为经严格测试、可直接参考并延伸使用的完整工程。资源包共63个文件压缩包约2.98MB其中包含Vue组件、JavaScript逻辑、CSS样式、HTML入口、JSON配置以及地图相关数据文件还配有PNG/JPG图片素材和字体文件目录结构兼顾源码、构建配置与静态资源便于按模块快速检索。源码已跑通完整流程读者可借助配套项目文档梳理由页面搭建到预警结果呈现的设计思路有利于完成课程设计答辩或积累实际工程经验。目前已有101人浏览学习对于希望以真实系统案例掌握VueJavaScript开发路径和地质灾害预警业务模型的开发者具有直接参考价值。1. 地质灾害预警系统为什么值得用 Vue 重做一遍山区县城的群测群防员每天要拿着纸质表格记录裂缝位移、降雨量、土壤含水率等数据层层汇总到县自然资源局往往已经过去两三天。真到了暴雨天隐患点的状态还是靠打电话问。用 Vue JavaScript 实现一套地质灾害预警系统核心目标不是训练一个多厉害的滑坡预测模型而是把“监测数据接入、预警规则判定、消息推送、处置闭环”这条链路做成一个能持续跑的前端系统。基于 Vue 3 组合式 API 组织代码用 ECharts 渲染降雨量趋势、用 Leaflet 标注隐患点空间分布再配合 Pinia 管理全局的监测数据和预警状态整个系统的代码量能控制在几千行以内。对本科毕业设计或课程设计来说这套技术栈还有一个现实优势不需要单独部署后端服务也能把完整流程跑起来答辩现场不会因为环境问题翻车。这个标题里的“源码 项目文档”指向的是一套可交付的工程模板所以本文会刻意避开“只讲概念”的写法把监测点管理、预警规则判定、消息中心和部署验证这几段都展开到能直接抄走的程度。2. Vue 技术选型与地质灾害预警系统的数据流设计2.1 为什么是 Vue 而不是纯 JavaScript 或 React纯 JavaScript 操作 DOM 实现一个预警大屏不是不行但一旦要维护监测点列表、实时数据面板、历史预警记录这些相互关联的状态时代码会迅速失控。React 的生态同样成熟但 Vue 在中国高校的课程设计和毕设中使用率更高参考代码多、组件库适配简单遇到问题更容易搜到解决方案。Vue 3 的组合式 API 把同一块业务逻辑例如某个监测点的时序数据获取收拢在一个setup作用域内配合ref和computed可以在不引入额外依赖的情况下写出可测试的纯函数——这对预警规则判定这种需要反复调参的模块尤其重要。2.2 系统边界前端为主、后端可延后一个常见的设计是把系统拆成两层表现第一层是数据接入即监测设备上报的降雨量、位移量、土壤含水率第二层是规则判定即根据阈值组合判断是否触发预警以及预警级别。后端如果用 Java Spring Boot 或 Node.js Express 当然可以但很多毕设场景下老师关注的重点是流程是否完整、界面是否可用、代码是否规范。所以下面的方案先假定后端接口还没完全就绪前端用 mock 数据层模拟设备上报后续用真实接口时只需替换 service 层的请求函数。2.3 工程目录与关键依赖npm create vuelatest geo-warning-system cd geo-warning-system npm install element-plus axios echarts leaflet pinia npm install -D unplugin-auto-import unplugin-vue-components建议使用 Vite 作为构建工具依赖安装速度明显快于 WebpackVue 3 生态默认也推荐 Vite。unplugin-auto-import和unplugin-vue-components用于按需引入 Element Plus 组件避免打包体积过大。创建完成后在vite.config.js里追加配置import { fileURLToPath, URL } from node:url import { defineConfig } from vite import vue from vitejs/plugin-vue import AutoImport from unplugin-auto-import/vite import Components from unplugin-vue-components/vite import { ElementPlusResolver } from unplugin-vue-components/resolvers export default defineConfig({ plugins: [ vue(), AutoImport({ resolvers: [ElementPlusResolver()] }), Components({ resolvers: [ElementPlusResolver()] }) ], resolve: { alias: { : fileURLToPath(new URL(./src, import.meta.url)) } }, server: { port: 5173, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } })上面这段配置解决两个问题import 路径简化/指向src/和开发环境的接口代理/api前缀的请求转发到后端 8080 端口。很多同学把接口请求写死成http://localhost:8080等到部署到服务器上就傻眼用代理的方式可以保证前端代码里只出现相对路径。2.4 数据模型与页面路由设计预警系统的数据模型建议拆成四个实体实体核心字段说明监测点id、名称、经度、纬度、等级、责任人空间数据用于地图标注监测数据监测点 id、时间、降雨量、位移量、含水率设备上报的时序数据预警记录监测点 id、级别、触发时间、处置状态规则判定后生成处置反馈预警记录 id、上报人、照片描述、处理意见闭环流程的末尾环节路由设计上保持 4 个页面/dashboard总览大屏、/monitor监测点列表、/warning预警消息中心、/history历史查询。如果毕设需要凑更多功能页可以补一个系统设置页但预警主流程不要分散到太多页面里。3. 地质灾害预警核心模块实现监测点地图、实时数据与规则判定3.1 用 Leaflet 渲染隐患点分布图地图是地质灾害预警系统区别于普通管理系统的第一个呈现差异点。Leaflet 是轻量级 JavaScript 地图库不需要高德或百度地图的 key 也能通过 OpenStreetMap 瓦片跑起来这对毕设场景非常友好。安装依赖后在组件里初始化地图// src/views/MonitorMap.vue import L from leaflet import leaflet/dist/leaflet.css import { onMounted, onBeforeUnmount, ref } from vue import { fetchMonitorPoints, fetchRealtimeData } from /api/monitor import { warningLevelColor } from /utils/warningLevel const mapRef ref(null) let mapInstance null let markerGroup null onMounted(async () { mapInstance L.map(mapRef.value, { center: [30.5, 108.4], zoom: 8, zoomControl: true }) L.tileLayer(https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png, { attribution: © OpenStreetMap contributors }).addTo(mapInstance) markerGroup L.layerGroup().addTo(mapInstance) await loadPoints() }) async function loadPoints() { const points await fetchMonitorPoints() const realtimeData await fetchRealtimeData() const dataMap new Map(realtimeData.map(item [item.pointId, item])) points.forEach(point { const rt dataMap.get(point.id) || {} const color warningLevelColor(rt.riskLevel || normal) L.circleMarker([point.lat, point.lng], { radius: 8, color: #fff, weight: 2, fillColor: color, fillOpacity: 0.8 }) .bindPopup( b${point.name}/bbr 等级${point.level}br 降雨量${rt.rainfall ?? -} mm/hbr 位移量${rt.displacement ?? -} mm ) .addTo(markerGroup) }) } onBeforeUnmount(() { if (mapInstance) { mapInstance.remove() mapInstance null } })warningLevelColor是一个纯函数输入风险等级返回对应颜色这在后面规则判定模块里还会复用。地图上的圆点颜色随实时数据变化当某个点位触发橙色或红色预警时图上会呈现醒目的色块这是大屏展示时的核心视觉锚点。注意onBeforeUnmount里必须销毁地图实例否则页面切换后 Leaflet 的事件监听器会残留导致内存泄漏和多次初始化时的地图白屏。3.2 Pinia 状态管理让监测数据在同一时刻保持一致预警系统的全局数据有很强的时序性和联动性监测点列表变化时地图组件、列表组件和图表组件需要同步刷新。用 Pinia 组织这些共享状态// src/stores/geoStore.js import { defineStore } from pinia import { fetchRealtimeData, triggerWarningCheck } from /api/monitor import { evaluateWarning } from /utils/warningEngine export const useGeoStore defineStore(geo, { state: () ({ points: [], realtimeData: [], warnings: [], lastUpdated: null, pollingTimer: null }), getters: { activeWarnings: (state) state.warnings.filter(w w.status ! resolved), highRiskPoints: (state) state.realtimeData.filter(d d.riskLevel red) }, actions: { async refreshData() { const [points, realtime] await Promise.all([ fetchMonitorPoints(), fetchRealtimeData() ]) this.points points this.realtimeData realtime this.lastUpdated new Date() // 针对每个点位执行规则判定返回新触发的预警 const newWarnings this.points .map(point { const rt this.realtimeData.find(d d.pointId point.id) return rt ? evaluateWarning(point, rt) : null }) .filter(Boolean) if (newWarnings.length 0) { this.addWarnings(newWarnings) } }, startPolling(interval 10000) { this.pollingTimer setInterval(() { this.refreshData() }, interval) }, stopPolling() { if (this.pollingTimer) { clearInterval(this.pollingTimer) this.pollingTimer null } } } })轮询间隔 10 秒是兼顾实时性和接口压力的常见选择答辩时可以说“参考了预警系统对分钟级数据更新的要求”。生产环境中更优的方案是 WebSocket 推送但作为毕设代码轮询的实现成本低、逻辑直观更容易讲清楚。evaluateWarning这个纯函数是规则引擎的入口下一节展开。3.3 预警规则引擎阈值组合与实效窗口规则判定是整个系统的技术核心。简单的做法是 if-else 写死但地质灾害预警要兼顾多个指标组合。比如“过去 24 小时降雨量超过 100mm 且当前小时降雨量超过 20mm”和“位移速率连续两小时超过 5mm/h”触发的级别不一样。把规则抽象成配置化的结构// src/utils/warningEngine.js const RULES [ { id: rain-24h-100, level: orange, metric: rainfall24h, operator: , value: 100, durationMinutes: 0, description: 24小时降雨量超过100mm }, { id: rain-1h-50, level: red, metric: rainfall1h, operator: , value: 50, durationMinutes: 0, description: 1小时降雨量超过50mm }, { id: displacement-rate-5, level: orange, metric: displacementRate, operator: , value: 5, durationMinutes: 120, description: 位移速率超过5mm/h且持续2小时 } ] export function evaluateWarning(point, realtimeData) { const now new Date() for (const rule of RULES) { const value realtimeData[rule.metric] if (value undefined || value null) continue if (rule.durationMinutes 0) { // 需要检查连续时长这里假定 realtimeData 里带了一个 history 字段 const history realtimeData.history || [] if (history.length 2) continue const recentItems history.slice(-2) const allMatch recentItems.every(item { return compareValue(item[rule.metric], rule.operator, rule.value) }) if (allMatch) { return makeWarning(point, rule, now) } } else { if (compareValue(value, rule.operator, rule.value)) { return makeWarning(point, rule, now) } } } return null } function compareValue(actual, operator, expected) { switch (operator) { case : return actual expected case : return actual expected case : return actual expected default: return false } } function makeWarning(point, rule, time) { return { id: warn-${point.id}-${time.getTime()}, pointId: point.id, pointName: point.name, level: rule.level, ruleId: rule.id, description: rule.description, createdAt: time.toISOString(), status: pending } }这套规则的巧妙之处在于把判定逻辑从 if-else 硬编码里解放出来。要新增一条“土壤含水率超过 30%”的黄色预警规则只需要在RULES数组里追加一项即可不需要改动evaluateWarning函数本体。规则里的durationMinutes字段处理的是时效窗口逻辑比如位移速率超标必须持续 120 分钟才触发避免瞬时抖动造成的误报。这个设计在答辩时是很好的加分点。3.4 同一轮轮询内的去重处理refreshData里每次轮询都会调用evaluateWarning如果不做去重同一个规则同一个点位会不停产生重复预警。在addWarnings里执行去重addWarnings(newWarnings) { const existingKeys new Set( this.warnings.map(w ${w.pointId}-${w.ruleId}) ) const fresh newWarnings.filter(w { const key ${w.pointId}-${w.ruleId} if (existingKeys.has(key)) return false existingKeys.add(key) return true }) if (fresh.length 0) { this.warnings [...this.warnings, ...fresh] } }去重的核心按键是pointId ruleId的组合意思是一个监测点对某条规则只能有一条未处置的预警。如果想支持同一监测点不同规则同时触发可以额外加一个level维度但绝大多数场景下一个点同时满足两条规则的场景很少按这个粒度去重已经足够。3.5 前端数据模拟层后端接口还没准备好时需要在前端模拟一套接口返回。在src/api/mock.js里// 模拟降雨量英文 rainfall单位毫米 let mockPoints [ { id: 1, name: 李家湾滑坡体, lat: 30.2, lng: 108.3, level: 橙色 }, { id: 2, name: 王家坪危岩, lat: 30.8, lng: 108.9, level: 黄色 } ] function randomRainfall() { return Math.random() * 60 } function generateRealtimeData() { return mockPoints.map(point { const rainfall24h randomRainfall() 80 return { pointId: point.id, rainfall1h: randomRainfall(), rainfall24h: rainfall24h, displacementRate: Math.random() * 6, history: [ { rainfall1h: randomRainfall(), rainfall24h: rainfall24h, displacementRate: Math.random() * 6 }, { rainfall1h: randomRainfall(), rainfall24h: rainfall24h, displacementRate: Math.random() * 6 } ] } }) } export function mockRealtimeData() { return new Promise(resolve { setTimeout(() resolve(generateRealtimeData()), 300) }) }mock 数据的随机范围要与真实物理场景大致相符降雨量在 0~60mm/h 之间波动、位移速率偶尔超过 5mm/h 触发规则这样演示时能自然看到预警产生。随机范围如果乱写比如降雨量动不动 200mm会导致页面一打开就飘满红色预警显得不可信。4. 预警消息中心从触发生成到处置闭环4.1 预警列表与状态流转预警消息中心是用户使用最频繁的功能。从规则引擎生成pending状态开始预警需要支持以下流转路径状态含义可执行操作pending新预警待确认确认为有效、误报confirmed已确认下发处置任务resolving处置中上传反馈resolved已处置查看处置记录列表页面的核心代码// src/views/WarningCenter.vue template el-table :datafilteredWarnings border stripe el-table-column proppointName label监测点 width140 / el-table-column proplevel label预警级别 width100 template #default{ row } el-tag :colorwarningLevelColor(row.level) {{ row.level }} /el-tag /template /el-table-column el-table-column propdescription label规则描述 min-width200 / el-table-column propcreatedAt label触发时间 width170 / el-table-column label操作 width220 fixedright template #default{ row } el-button v-ifrow.status pending sizesmall typeprimary clickconfirmWarning(row) 确认 /el-button el-button v-ifrow.status pending sizesmall typedanger clickmarkAsFalse(row) 误报 /el-button el-button v-ifrow.status confirmed sizesmall typewarning clickopenDisposalDialog(row) 处置 /el-button /template /el-table-column /el-table /template script setup import { computed, ref } from vue import { useGeoStore } from /stores/geoStore const geoStore useGeoStore() const statusFilter ref(all) const filteredWarnings computed(() { if (statusFilter.value all) return geoStore.warnings return geoStore.warnings.filter(w w.status statusFilter.value) }) function confirmWarning(row) { row.status confirmed } function markAsFalse(row) { row.status false row.description 【误报】原规则${row.description} } /script状态流转的逻辑放在组件里直接row.status xxx是毕设代码的常见写法快速、直观。如果想更规范可以在 Pinia 的 store 里封装updateWarningStatusaction 并对状态值做枚举校验但核心流程不变。4.2 历史预警趋势的可视化单看预警列表很难看出整体风险趋势需要在总览页加两个 ECharts 图表24 小时降雨量趋势和 30 天预警数量统计。降雨量趋势用折线图// src/views/Dashboard.vue 中的图表配置 import * as echarts from echarts import { onMounted, ref } from vue const chartRef ref(null) let chartInstance null onMounted(() { chartInstance echarts.init(chartRef.value) chartInstance.setOption({ title: { text: 24小时降雨量趋势(mm/h) }, tooltip: { trigger: axis }, xAxis: { type: category, data: [00:00, 02:00, 04:00, 06:00, 08:00, 10:00, 12:00] }, yAxis: { type: value }, series: [{ name: 李家湾降雨量, type: line, smooth: true, areaStyle: { opacity: 0.3 }, data: [8, 12, 20, 35, 28, 15, 9] }] }) })用areaStyle给折线图加一层半透明渐变面积数据差异在视觉上更有冲击力。注意初始化图表前要确认chartRef对应的 DOM 已经渲染完成onMounted是安全的时机。4.3 处置反馈的动态渲染处置弹窗里让用户填写处理措施和现场照片描述关闭后把反馈内容写入预警记录再在下拉面板里展示function openDisposalDialog(row) { disposalTarget.value row disposalForm.value { handler: , measure: , photos: } disposalDialogVisible.value true } function submitDisposal() { disposalTarget.value.status resolving disposalTarget.value.feedback { ...disposalForm.value, submittedAt: new Date().toISOString() } disposalDialogVisible.value false }这里没有真正上传图片只用文字描述代替毕设阶段足够。如果要更真实可以用 Element Plus 的el-upload 后端文件接口但功能主链路不受影响。5. 从 mock 数据到真实接口替换成本与部署验证5.1 接口层设计成可替换的形态前面所有代码里数据获取都是通过/api/monitor这层封装调用的。从 mock 切换到真实后端时只需要改这个文件// src/api/monitor.js import { mockRealtimeData, mockPoints, mockWarnings } from ./mock // 切换开关false 使用 mocktrue 使用真实接口 const USE_MOCK true export function fetchMonitorPoints() { if (USE_MOCK) { return Promise.resolve(mockPoints) } return axios.get(/api/points).then(res res.data) } export function fetchRealtimeData() { if (USE_MOCK) { return mockRealtimeData() } return axios.get(/api/realtime).then(res res.data) }真实接口联调时把USE_MOCK改为false即可页面组件层完全不用动。这个设计可以直接写进项目文档的“接口替换指南”章节也是答辩时展示工程能力的一个细节。5.2 打包部署与常见布局问题npm run build构建产物在dist/目录下部署到 Nginxserver { listen 80; server_name your-domain.com; root /var/www/geo-warning-system/dist; index index.html; location / { try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8080; } }try_files这行必须写否则刷新/warning这样的深层路由时会报 404。很多同学打包后在本地双击index.html打开是完全空白的这主要是因为 Vue Router 默认使用createWebHistory模式文件协议下路径解析不了。打包部署到服务器前可以在src/router/index.js里改用createWebHashHistory规避这个坑或者坚持用 history 模式配好 nginx 的 try_files。5.3 验证清单检查系统是否真正闭环跑完整个流程后按下面顺序走一遍验收打开/dashboard地图正常加载监测点圆点有颜色区分。mock 数据每 10 秒刷新一次某个点位的降雨量超过阈值时右上角出现新预警提示。进入/warning新预警状态为 pending点击“确认”后状态变为 confirmed。点击“处置”填写反馈状态变为 resolving。回到/history按时间筛选当天的预警记录确认处置链条完整。刷新页面Vue Router 不报 404按 F12 检查 Network没有跨域报错。最后检查一遍npm run build的产物大小Element Plus 按需引入后通常能控制在 500KB 以内如果超过 1MB 检查是否误把完整库打包进来了可以用vite-plugin-compression开启 gzip 压缩。本文还有配套的精品资源点击获取