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

资讯详情

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

React + antd 环境监测后台管理系统建设实践

React + antd 环境监测后台管理系统建设实践 简介这是一份基于 React 与 antd 的环境监测网站课程设计完整资源面向正在完成前端项目实践或需要参考中后台交互方案的高校学生与初级开发者。系统采用 B/S 架构前端以 Reactantd 组件库为核心融入 AJAX、HTML5、CSS3 等技术实现环境数据上传、查看、项目分配与工作分类管理内置 mock 数据并能在页面不刷新情况下暂存数据整体覆盖数据分析、数据监控、用户工作台、财务管理、项目管理及人员管理六大功能模块。压缩包共 98 个文件以 jsx 组件源码、css 样式、png 界面截图为主另有 json 配置、webpack 打包配置、说明文档及毕业论文 docx包体仅 3.96MB目录结构清晰便于分模块阅读和二次开发。目前已有 176 人学习下载适合需要系统掌握 Reactantd 中后台开发流程、组件拆分思路与模拟数据交互的读者参考。1. 环境监测网站不是一张大屏拆开 React antd 的六块业务边界很多人拿到环境监测网站的第一反应是“放一堆图表在大屏上”但真正动手用 React antd 做过后台管理系统的人会告诉你难点从来不在画图而在业务状态怎么流转。这个项目让我印象最深的是它没有把精力耗在炫酷特效上而是老老实实分了六个板块数据分析、数据监控、用户工作台、财务管理、项目管理、人员管理。整体采用 B/S 架构前端基于 React antd数据先用 mock JSON 驱动页面不刷新也能保留操作结果。这种做法非常适合课程设计、毕设和中小团队内部系统起步低门槛、组件齐、状态可控。下面我会按实际开发顺序把这套系统的关键设计拆开讲从工程化搭建、数据接入、状态管理到最后 antd Table 的动态列合并技巧每个环节都给出可直接抄的配置和代码。2. 从零搭出可维护的 React antd 工程webpack、babel 与目录边界2.1 为什么不用 Create React App 而要手写 webpack 配置现在的脚手架默认隐藏 webpack但这个项目里保留了完整的webpack.config.js和webpack.production.config.js。我在接管这类代码时并不觉得多余反而认为这是最值得讲清楚的部分。手写 webpack 的价值在于你能精确控制入口、别名和 mock 转发这些在环境监测这种需要对接大量 JSON 数据的场景里非常实用。先看开发环境的入口配置我简化掉不必要的插件后是这样const path require(path); const HtmlWebpackPlugin require(html-webpack-plugin); module.exports { entry: ./src/main.jsx, output: { path: path.resolve(__dirname, dist), filename: bundle.[contenthash:8].js }, resolve: { extensions: [.js, .jsx, .json] }, module: { rules: [ { test: /\.jsx?$/, exclude: /node_modules/, use: { loader: babel-loader, options: { presets: [babel/preset-env, babel/preset-react] } } }, { test: /\.css$/, use: [style-loader, css-loader] } ] }, devServer: { port: 3000, hot: true, historyApiFallback: true } };这段配置里有三个参数值得说明。entry指向main.jsx这是所有组件的起点后面接入 Redux 时也要从这里包一层Provider。extensions让 JSX 文件可以不带后缀引入省去一堆import x from ./x.jsx。historyApiFallback必须打开因为 React Router 用的可能是 BrowserRouter路由路径在刷新时会 404。2.2 目录结构决定了状态流的边界原项目里除了 webpack 配置还会看到app、containers、components、routes、stores、actions、constants、reducers、static这样一套目录。这套结构在 2024 年的 React 社区里仍然不过时因为它把“页面容器”“纯组件”“全局状态”三个层次切得很干净。我一般会建议按这个边界来组织src/ ├── components/ # 无状态组件按钮区、表格列、图表卡片 ├── containers/ # 有业务逻辑的页面容器连接 store ├── routes/ # 路由表单独拆出来方便做权限控制 ├── stores/ # 创建单一 store ├── actions/ # action creator ├── reducers/ # 状态更新逻辑 ├── constants/ # action type 常量 ├── static/ # 静态资源、mock 图片 └── main.jsx # 入口文件很多新人在做环境监测这类系统时会把“数据获取”直接写进components结果按钮组件里出现axios调用这个模块马上就不干净了。正确方式是 components 只负责显示和抛出事件数据请求放在containers或单独的服务层。原项目里的图片编号从 1 到 25基本对应各个页面的截图当时应该是为了写论文用的说明作者对界面完整度很在意。2.3 antd 组件选型以“表格 表单 布局”为核心环境监测后台的重头戏不是组件库有多全而是能不能快速拼出信息密度高的管理界面。antd 在这类 B 端场景里优势非常明显Table、Form、Layout、Menu是四个高频件几乎每个板块都用得上。比如项目管理的页面一定会长这样import { Table, Tag, Button, Space } from antd; const columns [ { title: 项目编号, dataIndex: id, width: 100 }, { title: 项目名称, dataIndex: name }, { title: 状态, dataIndex: status, render: (status) ( Tag color{status 进行中 ? green : red}{status}/Tag ) }, { title: 操作, render: (_, record) ( Space Button sizesmall onClick{() handleAllocate(record)}分配/Button Button sizesmall danger onClick{() handleDelete(record)}删除/Button /Space ) } ];Space用来做按钮间距Tag用来区分环境监测项目的执行状态操作列的 render 函数能直接拿到当前行数据。这个例子说明 antd 表格不需要引入额外封装的业务组件就能覆盖大部分“查看环境数据 分配项目”的需求。3. 数据监控与趋势图Mock JSON、页面级存储与图表渲染3.1 Mock 数据的两种常见做法原项目明确说“数据将使用自己定义的 mock 数据”并且要求“在页面不重新启动的情况下有保存数据的功能”。这句话翻译过来就是需要一个本地状态的 mock server或者至少是一个模块级内存 store。我见过很多人用 json-server 起一个全局 mock但环境监测数据一旦重新刷新就全丢了与需求不符。更稳妥的做法是在前端实现一个模拟接口层。例如新增环境监测数据时数据先写入模块内的数组再模拟网络延迟返回// mock/api.js let monitorList []; export const fetchMonitorData () { return new Promise((resolve) { setTimeout(() resolve({ code: 200, data: monitorList }), 200); }); }; export const uploadMonitorData (payload) { return new Promise((resolve) { monitorList [payload, ...monitorList]; setTimeout(() resolve({ code: 200, message: 上传成功 }), 200); }); };使用这样的 mock 层时页面组件不需要感知数据是不是真的统一走async/await调用。这样做的好处是将来接真实接口时只需替换mock/api.js内部实现所有调用方都不用动。3.2 用 antd Table ECharts 展示实时监控数据数据监控页的核心是两张图趋势折线图和实时数据表格。ECharts 在 React 里最常用的封装方式不是引入重型组件而是直接用useEffect初始化实例这样可控性最高。import { useEffect, useRef } from react; import * as echarts from echarts; import { Card } from antd; export default function MonitorChart({ data }) { const chartRef useRef(null); useEffect(() { const chart echarts.init(chartRef.current); chart.setOption({ tooltip: { trigger: axis }, xAxis: { type: category, data: data.map(item item.time) }, yAxis: { type: value, name: PM2.5 }, series: [{ name: 空气质量指数, type: line, smooth: true, data: data.map(item item.value) }] }); return () chart.dispose(); }, [data]); return Card title实时监测趋势div ref{chartRef} style{{ height: 320 }} //Card; }这里的useEffect依赖data只要上传接口返回新数据图表就会自动刷新。chart.dispose()在组件卸载时销毁实例避免内存泄漏。数据监控页除了图表还会配一个 antd 的Table和图表共用同一个data源但从不同的维度展示。表格列里通常会有数值单位和超标状态两列状态列用Tag颜色区分比如TAG_COLOR { 正常: green, 预警: orange, 超标: red }。3.3 页面不重启保存数据的状态管理细节需求里提到的“保存数据”本质上是把数据从组件内部的useState提升到更外层。最简单的方式是放到reducer里因为 Redux store 在页面整个生命周期内都常驻。有人会用sessionStorage但它需要序列化而且只能存字符串在复杂对象上容易出 bug。我建议遵守原项目的设计所有新增数据通过 action 提交到 reducer例如// actions/monitor.js import { ADD_MONITOR_DATA } from ../constants/actionTypes; export const addMonitorData payload ({ type: ADD_MONITOR_DATA, payload });然后在 reducer 中这样写入const initialState { monitorRecords: [] }; export default function monitorReducer(state initialState, action) { switch (action.type) { case ADD_MONITOR_DATA: return { ...state, monitorRecords: [action.payload, ...state.monitorRecords] }; default: return state; } }这个动作完成后即使切换到其他页面再切回来数据依然在。唯一需要注意的是 Redux 默认不支持持久化浏览器刷新后还是会丢但原需求只要求“页面不重新启动”所以完全达标。如果将来要刷新不丢得再接入redux-persist或者本地缓存那是另一个话题。4. 用户工作台与权限路由Redux 数据流和 Roles 的落地做法4.1 工作台为什么需要 Redux 而不是 Context这个系统里有用户工作台、财务管理和人员管理多个页面之间会共享当前用户、待办任务和项目分配状态。如果只用useContext状态一变所有消费组件都会重渲染性能不好控制。Redux 的connect或useSelector则能精确订阅需要的片段。原项目把actions、constants、reducers、stores分开正是在为标准 Redux 数据流做准备。数据结构可以这样定义// reducers/user.js const initialState { currentUser: null, todoList: [], role: }; // constants/actionTypes.js export const SET_CURRENT_USER SET_CURRENT_USER; export const UPDATE_TODO_LIST UPDATE_TODO_LIST;工作台页面加载时会先派发fetchCurrentUser这个 action然后通过useSelector读取用户信息和控制台统计卡片。典型代码是import { useSelector, useDispatch } from react-redux; import { fetchCurrentUser } from ../actions/user; import { Card, List, Avatar } from antd; import { useEffect } from react; const Workbench () { const dispatch useDispatch(); const { currentUser, todoList } useSelector(state state.user); useEffect(() { dispatch(fetchCurrentUser()); }, [dispatch]); return ( Card title{${currentUser?.name || 未知用户} 的工作台} List dataSource{todoList} renderItem{(item) ( List.Item List.Item.Meta avatar{Avatar{item.type[0]}/Avatar} title{item.title} description{item.priority high ? 高优先级 : 普通} / /List.Item )} / /Card ); };这里useSelector的参数是回调函数返回state.user对象。需要注意如果返回的是新的对象引用哪怕内容没变也可能触发渲染。保险起见分开取currentUser和todoList两个useSelector调用更稳妥。4.2 路由表怎么配合人员管理与财务模块人员管理和财务管理应该是不同角色看到的所以路由不能静态写死。常见做法是配置私有路由组件用role字段做拦截import { Navigate } from react-router-dom; const PrivateRoute ({ role, children }) { const userRole useSelector(state state.user.role); if (!userRole) return Navigate to/login replace /; if (role userRole ! role) return Navigate to/403 replace /; return children; };把这个组件套在路由上Route path/finance element{ PrivateRoute roleadmin FinancePage / /PrivateRoute } /在环境监测这个场景里普通用户应该能看数据监控和数据分析只有管理员能进财务和人员管理。这个role也可以是数组比如[admin, manager]需要做数组包含判断。路由守卫不是安全边界它只是控制 UI 入口真正安全依然要在后端做鉴权。4.3 工作分类与项目分配的状态联动项目分配是整个系统里最容易写乱的部分。分配项目时会涉及三个模块项目列表、用户工作台、人员管理。比如管理员在“项目管理”里把项目分配给某人这个人的“工作台待办”应立即多一条记录。用 Redux 可以这样设计const onAllocate (projectId, userId) { dispatch(allocateProject(projectId, userId)); dispatch(updateTodoList(projectId, userId)); };但更合理的做法是封装一个业务 service内部连续派发两个 action组件只调用一个函数。在 reducer 里要注意两个状态更新的边界项目分配是把project.userId改成目标用户待办列表是往user.todoList里追加一条数据。两边不共享字段所以不推荐用一个 reducer 强行处理所有模块按领域拆 reducer 才便于维护。5. antd Table 动态合并列与监控告警表格的优化技巧环境监测的数据监控页常常需要把同一监测点、同一时间的多行数据列合并展示样式上更接近“分组报告”。antd 的Table内置onCell方法支持动态合并做法是给每一个单元格返回rowSpan该值为 0 时隐藏为大于 1 的整数时向下合并。const mergeCells (data, key) { const result {}; data.forEach((row, index) { const value row[key]; if (result[value]) { result[value].end index; } else { result[value] { start: index, end: index }; } }); return result; }; const columns [ { title: 监测点, dataIndex: station, onCell: (record, index) { const range mergeCells(data, station)[record.station]; if (!range) return {}; if (index range.start) { return { rowSpan: range.end - range.start 1 }; } return { rowSpan: 0 }; } }, { title: 时间, dataIndex: time }, { title: 指标, dataIndex: metric }, { title: 数值, dataIndex: value } ];这段代码里mergeCells先把同一监测点出现的起止行算出来然后只在起始行返回rowSpan其余行返回 0antd 会自动隐藏格子并完成纵向合并。需要注意onCell的第二个参数是行索引当表格分页时它指的是当前页内的索引所以合并计算必须基于当前页的data不能基于全部数据。数据量大的话建议把mergeCells的结果缓存成 Map不要每行每次都重新遍历否则 1000 行内就能感受到卡顿。最后补一个常用的性能优化点当监控数据每 5 秒刷新一次时表格会频繁重渲染。我给Table的rowKey设成record.id同时给每列加shouldCellUpdate控制只有数值变化时才更新单元格。再配合pagesize控制在 20 行以内antd Table 在这种轻量后台场景下完全扛得住。动态合并列和性能优化是两个独立的技巧建议先写出不合并的版本确认数据正确后再加rowSpan避免排查问题时分不清是数据错还是合并逻辑错。本文还有配套的精品资源点击获取
返回列表