资讯详情

企业级Python 自动化运维实战(六):Vue前端管理页面

📅 2026/10/15 18:48:52 | 华诺云谱 👁 阅读
企业级Python 自动化运维实战(六):Vue前端管理页面
专栏导语欢迎来到《企业级Python自动化运维实战》专栏的第六篇经过前面几个版本的迭代我们的系统已经从一个能跑的脚本进化成了一个规范的后端服务——有 FastAPI 接口、有 SQLite 持久化、还有 Swagger 自动文档。我们要完成从后端自动化工具到前后端分离的 SRE 巡检管理平台的关键一跃给系统装上可视化界面让任何人打开浏览器、点两下鼠标就能完成巡检并看懂趋势。本章你能学到Vue3 Vite 项目的搭建与目录组织方式Axios 封装以及与 FastAPI 的联调CORS 跨域的原理与解决办法组件化开发父组件传值、子组件 Props 接收ECharts 异步数据渲染的坑与解法1.V2.4版本整体架构先建立全局认知看清楚数据是怎么一层层流动起来的浏览器 | Vue3 ← 负责看页面展示与交互 | HTTP请求(Axios) | FastAPI ← 负责做业务逻辑与数据处理 | ----------------------- | | SQLite 巡检任务 历史数据存储 真正去连服务器采集这张图体现了前后端分离的核心思想前端只管展示和交互后端只管数据和逻辑两边通过 HTTP 接口API对话。这么做的好处是职责清晰、各自独立。方便后续把前端换成 React、或者想给系统加一个手机 App后端代码是不需要改动的。2.技术选型技术作用为什么选它Vue3页面框架渐进式框架上手平缓中文文档完善对 Python 转前端的同学非常友好Vite项目构建Vue 官方推荐的新一代构建工具启动速度远快于老牌 Webpack改代码后热更新几乎无延迟Axios调用API最主流的 HTTP 请求库支持拦截器、超时控制比浏览器原生 fetch 更好用Element PlusUI组件开箱即用的企业级组件库按钮、卡片、标签、弹窗直接拿来用不用自己写 CSSECharts数据图表国产开源可视化库折线图/柱状图/仪表盘应有尽有中文文档齐全3.创建Vue项目前置条件安装 Node.js 并配置好环境变量建议 Node 16 及以上版本详细安装教程本文不展开。先确认环境没问题终端执行node-vnpm-v两条命令都能输出版本号说明安装成功。接着在项目根目录也就是后端api/文件夹所在的那一层打开终端执行npmcreate vitelatest server-inspection-web执行后会有几个交互式选项提示是否安装create-vite输入y回车选择框架方向键选择Vue选择语言选择JavaScript先不碰 TypeScript降低上手门槛。看到Done. Now run:提示说明脚手架创建完成。进入目录、安装依赖、启动cdserver-inspection-webnpminstallnpmrun dev访问http://localhost:5173就能看到 Vue 的欢迎页面。 至此前端和后端是平级的两个目录各跑各的服务后端占 8000 端口前端占 5173 端口互不影响。4.安装项目依赖npminstallaxios element-plus echarts这三个依赖分别对应上表中的调用API“UI组件”“数据图表”。装完打开package.json能在dependencies里看到它们及版本号。 如果npm install很慢可临时切国内镜像npm config set registry https://registry.npmmirror.com5.调整目录结构脚手架生成的文件里有一堆示例内容如HelloWorld.vue、style.css我们先把目录整理成项目真正需要的样子。前端配置统一放在server-inspection-web目录下server-inspection-web ├── index.html ← 页面入口 HTML ├── package.json ← 依赖清单与启动脚本 ├── vite.config.js ← Vite 配置后续配代理会用到 └── src ├── api │ └── request.js ← axios 封装统一负责和 FastAPI 通信 ├── components │ ├── ServerCard.vue ← 展示单台服务器的卡片 │ └── TrendChart.vue ← ECharts 历史趋势图 ├── views │ └── Dashboard.vue ← 首页/控制中心巡检主页面 ├── App.vue ← 根组件所有页面的容器 └── main.js ← 程序入口启动整个 Vue每个目录的职责api/所有和后端打交道的请求都放这里页面组件不直接写 URL方便统一维护components/可复用的小组件一张卡片、一个图表哪里需要就往哪里放views/页面级组件一个 view 通常对应一个完整页面。 后面页面变多时还会加入router/目录做路由跳转比如独立的 History 历史页。当前阶段先聚焦首页保持最小可用结构。完整请求链路——从浏览器打开页面到数据渲染出来中间发生了什么浏览器 ↓ index.html ← 浏览器加载的唯一 HTML 文件 ↓ main.js ← 启动整个 Vue 应用 ↓ App.vue ← 根组件 ↓ Dashboard.vue ← 负责巡检页面的业务逻辑 ↓ ↓ ↓ ↓ request.js ServerCard.vue ← 负责显示一台服务器 负责和 FastAPI 通信 ↓ Axios ↓ http://127.0.0.1:8000 ↓ FastAPI ↓ SQLite理解这条链路很重要以后页面出问题你就知道该从哪一段排查——是请求没发出去Axios、后端没返回FastAPI还是数据没渲染Vue。6.配置前端启动主入口src/main.js是整个前端程序的起点相当于后端的if __name__ __main__。import{createApp}fromvue;importElementPlusfromelement-plus;importelement-plus/dist/index.css;importAppfrom./App.vue;constappcreateApp(App);app.use(ElementPlus);app.mount(#app);逐行拆解代码作用import { createApp } from vue从 vue 中导入创建应用的工厂函数import ElementPlus from element-plus导入 Element Plus 组件库import element-plus/dist/index.css只加载样式文件不接收任何导出内容createApp(App)以 App.vue 为根组件创建 Vue 应用实例app.use(ElementPlus)全局注册组件库之后所有子组件都能直接用el-button不用在每个 .vue 里重复 importapp.mount(#app)把 Vue 应用挂载到 index.html 中idapp的 div 上整体流程main.js ↓ 启动 Vue 实例 ↓ 加载 App.vue ↓ 挂载到 index.html 的 #app 节点 ↓ 浏览器看到完整页面知识点JavaScript 的导入和 Python 不太一样Python 里我们习惯import xxx或from xxx import yyy。JS 的导出分两种导入写法也随之不同① 命名导出Named Export导出方用export const指定名字一个文件可以有多个。导入时必须用大括号指定要导入哪个exportconstnameTom;exportfunctionsayHi(){}// 导入import{name,sayHi}from./demo.js;上面import { createApp } from vue就是命名导出——createApp是 vue 包明确导出的名字不能随便改。② 默认导出Default Export一个文件只能有一个默认导出。导入时不需要大括号名字可以自己起exportdefault{}// 下面两种写法都合法名字随便取importAppfrom./App.vue;importMyRootfrom./App.vue;那么问题来了App.vue 里并没有写export default为什么能用默认导入因为 Vite/Vue 编译器会把.vue单文件组件自动处理成一个可被导入的组件模块等价于帮你做了默认导出。所以我们写import App from ./App.vue即可App只是我们在当前文件里给它起的本地名字。一句话记忆有名字用大括号没名字自己起名。7.配置Axios请求解决跨域问题7.1后端调用Axios 是一个 JavaScript 的 HTTP 请求库作用是让前端能够访问后端 API专门负责前端和 FastAPI 之间的 HTTP 通信。我们创建一个专门用于访问 FastAPI 后端接口的 HTTP 请求对象之后 Vue 页面不用每次写完整地址只需要调用这个封装好的request即可。src/api/request.jsimportaxiosfromaxios;constrequestaxios.create({baseURL:http://127.0.0.1:8000/api,timeout:5000});exportdefaultrequest;两个参数的意思baseURL接口地址的公共前缀。以后写request.get(/history)实际请求的就是http://127.0.0.1:8000/api/historytimeout超时时间毫秒超过 5 秒没响应就报错避免页面一直转圈。为什么要把地址抽出来如果哪天后端换了端口或部署到服务器你只需要改这一个文件而不用去几十个页面里一个个改 URL。这就是封装的价值。7.2跨域问题前端 Vue 跑在localhost:5173后端 FastAPI 跑在localhost:8000端口不同这就触发了浏览器的同源策略。什么是同源协议、域名、端口三者完全相同才算同源。我们的协议都是 http、域名都是 localhost但端口一个是 5173 一个是 8000所以属于跨域。浏览器出于安全考虑默认会拦截跨域请求控制台会看到类似这样的报错Access to XMLHttpRequest has been blocked by CORS policy解决办法是在后端开启 CORS跨域资源共享明确告诉浏览器我允许这个来源访问我。修改api/main.pyfromfastapi.middleware.corsimportCORSMiddleware app.add_middleware(CORSMiddleware,allow_origins[http://127.0.0.1:5173,http://localhost:5173],allow_credentialsTrue,allow_methods[*],allow_headers[*])app.add_middleware相当于给 FastAPI 安装一个中间处理程序它可以在请求进入接口之前、响应返回浏览器之前做一些统一处理。跨域校验就放在这一层。参数说明参数作用allow_origins允许哪些来源访问。⚠️ 必须写完整协议域名端口127.0.0.1和localhost在浏览器眼里是两个不同的源所以两个都写上allow_credentials允许携带凭证信息例如 Cookieallow_methods允许所有 HTTP 请求方法GET/POST/PUT/DELETE…allow_headers允许前端携带各种请求头 生产环境不要把allow_origins写成[*]应当只放行你自己的前端域名否则任何人都能调你的接口。8.创建首页Dashboard这是我们平台的首页/控制中心属于业务逻辑层 页面层负责1. 获取后端的服务器历史数据 2. 保存这些数据 3. 把数据交给 ServerCard 显示 4. 用户点击立即巡检时通知后端开始巡检 5. 页面打开时自动加载历史数据先理解.vue文件的三段式结构一个.vue文件单文件组件SFC可以理解为template → 页面长什么样HTML script setup → 页面怎么工作JS 逻辑 style → 页面什么样式CSSsrc/views/Dashboard.vuetemplate div h2服务器巡检平台/h2 !-- 点击后调用 script 中定义的 check 方法 -- el-button typeprimary clickcheck 立即巡检 /el-button div classserver-list !-- 遍历 servers每台服务器渲染一张卡片 -- ServerCard v-foritem in servers :keyitem.server :dataitem / /div /div /template script setup import { ref, onMounted } from vue; import ServerCard from ../components/ServerCard.vue; import request from ../api/request; const servers ref([]); async function loadData() { const res await request.get(/history); servers.value res.data; } async function check() { await request.post(/check); alert(巡检任务已启动); } // 页面加载完成后自动拉取一次历史数据 onMounted(() { loadData(); }); /script几个关键点①ref()是响应式的核心ref([])会把数据包装成响应式对象。可以理解成servers{value:[]}所以必须用servers.value才能拿到真正的数据赋值时也要写servers.value xxx。这样一旦数据变化页面会自动跟着刷新——不需要手动操作 DOM。②async / await处理异步网络请求是需要时间的。await request.get(...)表示等这个请求返回结果再往下走否则servers.value拿到的会是 undefined。③res.data而不是resAxios 的返回值res里包含data、status、headers等一堆信息后端真正返回的业务数据在res.data里。④onMounted生命周期钩子页面组件挂载完成后自动执行里面的函数适合放打开页面就要加载的逻辑。⑤ 模板里的两个 Vue 语法v-foritem in servers循环渲染有几台服务器就渲染几张卡片:keyitem.server给每个循环项一个唯一标识帮助 Vue 高效更新列表:dataitem冒号是 Vue 的动态绑定语法。加了冒号Vue 会去找item这个变量的实际值不加冒号传过去的就是字符串item本身。数据流向Dashboard.vue ↓ “我要历史数据” ↓ request.js ↓ Axios ↓ FastAPIalert()是浏览器原生的弹窗能用但比较简陋。想更专业可以换成 Element Plus 的消息提示import{ElMessage}fromelement-plus;ElMessage.success(巡检任务已启动);9.创建服务器卡片组件这个组件负责单台服务器的展示。把它单独拆出来的好处是Dashboard 只关心有哪些服务器卡片自己关心一台服务器怎么显示职责分离以后改样式不影响主页。src/components/ServerCard.vuetemplate el-card h2{{ data.server }}/h2 pCPU: {{ data.cpu }}%/p pMemory: {{ data.memory }}%/p pDisk: {{ data.disk }}%/p !-- 根据状态显示不同颜色的标签 -- el-tag :typedata.status success ? success : danger {{ data.status }} /el-tag /el-card /template script setup // 声明当前组件接收父组件传过来的数据 defineProps({ data: Object }); /script知识点{{ }}插值表达式把data.server的值渲染到页面上。数据变了显示自动变defineProps声明我要接收哪些外部数据。这里声明了一个名为data、类型为Object的属性。父组件通过:dataitem传进来子组件直接用data.xxx访问三元表达式条件 ? 满足时的值 : 不满足时的值。这里表示状态是success就显示绿色标签否则显示红色标签。 这就是 Vue 的父子组件通信父组件用:绑定传值子组件用defineProps接收。数据只能从父流向子单向数据流子组件不要直接修改 props。10.修改App.vueApp.vue是根组件所有页面都挂在它下面。当前阶段我们直接把首页放进来。src/App.vuetemplate Dashboard/ /template script setup import Dashboard from ./views/Dashboard.vue; /script完成后启动项目验证效果# 启动后端uvicorn api.main:app--reload# 启动前端另开一个终端在 server-inspection-web 目录下npmrun dev浏览器打开 http://localhost:5173就能看到服务器巡检平台的页面展示每台服务器的 CPU、内存、磁盘和状态标签。⚠️ 注意后端必须先启动否则页面能打开但数据加载不出来控制台会报网络错误。11.增加历史趋势页面目前只能查看每次巡检的结果数据对比不够直观。我们增加趋势页面观察走势提前发现问题——趋势比单点更有价值CPU 从 30% 涨到 80% 的过程比一次 80% 的快照更值得警惕。思路是先写后端接口再做前端呈现。11.1新增后端趋势接口修改api/history.py在已有导入的基础上补充下面代码块中前几行为该文件需要的上下文若已存在则无需重复添加fromfastapiimportAPIRouter,Queryfromdatetimeimportdatetime,timedelta routerAPIRouter()router.get(/history/{server_name}/trend)defserver_trend(server_name:str,days:intQuery(7,ge1,le90,description查询最近N天数据)):dbget_db()startdatetime.now()-timedelta(daysdays)records(db.query(InspectionRecord).filter(InspectionRecord.server_nameserver_name,InspectionRecord.create_timestart).order_by(InspectionRecord.create_time).all())db.close()return{server:server_name,time:[r.create_time.strftime(%m-%d %H:%M)forrinrecords],cpu:[r.cpuforrinrecords],memory:[r.memoryforrinrecords],disk:[r.diskforrinrecords]}不要忘记在主程序中注册这个路由若前面章节已注册过则跳过。逐段解析代码说明from datetime import datetime, timedeltatimedelta是 datetime 模块里用于表示时间差的类做日期加减、时间差计算的核心工具days: int Query(7, ge1, le90)Query用来处理 URL 中?days7这类查询参数。ge1、le90表示最小 1、最大 90超出范围 FastAPI 会自动返回 422 错误——参数校验不用自己写 if 判断datetime.now() - timedelta(daysdays)计算起始时间比如 7 天前.filter(...)两个条件服务器名匹配 记录时间晚于起始时间.order_by(...)按时间由早到晚排序这样画出来的折线图才不会左右颠倒列表推导式[r.cpu for r in records]等价于 for 循环把所有记录的 cpu 值收集成一个列表ECharts 要的就是这种格式启动后端后可以访问 http://127.0.0.1:8000/docs 测试这个接口传入服务器名和天数确认能返回三组数组。11.2创建ECharts图表组件ECharts 是一款基于 JavaScript 的开源数据可视化图表库。创建src/components/TrendChart.vuetemplate div refchartRef styleheight: 300px; width: 100%; / /template script setup import { onMounted, onBeforeUnmount, ref, watch } from vue; import * as echarts from echarts; const props defineProps({ title: { type: String, default: 趋势图 }, times: { type: Array, default: () [] }, values: { type: Array, default: () [] } }); const chartRef ref(null); let myChart null; function renderChart() { if (!myChart) return; myChart.setOption({ title: { text: props.title }, tooltip: { trigger: axis }, xAxis: { type: category, data: props.times }, yAxis: { type: value, min: 0, max: 100 }, series: [{ data: props.values, type: line, smooth: true }] }, true); // true 表示不合并旧数据直接替换 } onMounted(() { if (chartRef.value) { myChart echarts.init(chartRef.value); renderChart(); } }); // 关键数据是异步请求回来的onMounted 时还是空数组 // 所以要用 watch 监听 props 变化数据到了再重画 watch( () [props.times, props.values], () { renderChart(); }, { deep: true } ); // 窗口缩放时图表自适应 const onResize () myChart myChart.resize(); window.addEventListener(resize, onResize); onBeforeUnmount(() { window.removeEventListener(resize, onResize); if (myChart) { myChart.dispose(); myChart null; } }); /script三个最容易踩的坑① 为什么需要refchartRefECharts 需要一个真实的 DOM 元素来挂载图表。ref让我们能在 JS 里拿到这个 div再交给echarts.init()。② 为什么必须用watch监听最重要这是新手最常困惑的地方。数据是后端异步请求回来的而onMounted执行的时候请求可能还没完成props.times还是空数组——这时候画出来就是一张空图。所以必须用watch监听数据变化等数据真正到了再重画一次。{ deep: true }表示深度监听数组内部元素变化也能感知到。③ 为什么要在onBeforeUnmount里销毁ECharts 实例和 window 上的 resize 监听不会自动消失。如果不清理组件反复切换会造成内存泄漏。这是写出健壮前端代码的习惯。11.3改造Dashboard整合趋势图修改src/views/Dashboard.vue把趋势图区域加进来template div h2服务器巡检平台/h2 el-button typeprimary clickcheck立即巡检/el-button !-- 趋势图区域有数据才显示 -- div v-iftrend.time.length stylemargin-top: 20px; h3{{ selectedServer }} 历史趋势/h3 div styledisplay: flex; flex-wrap: wrap; div stylewidth: 33%; TrendChart titleCPU :timestrend.time :valuestrend.cpu / /div div stylewidth: 33%; TrendChart title内存 :timestrend.time :valuestrend.memory / /div div stylewidth: 33%; TrendChart title磁盘 :timestrend.time :valuestrend.disk / /div /div /div !-- 没数据时的提示 -- div v-else stylemargin-top: 20px; color: #999; 暂无趋势数据请先执行巡检 /div div classserver-list ServerCard v-foritem in servers :keyitem.server :dataitem / /div /div /template script setup import { ref, onMounted } from vue; import ServerCard from ../components/ServerCard.vue; import request from ../api/request; import TrendChart from ../components/TrendChart.vue; const servers ref([]); const selectedServer ref(); const trend ref({ time: [], cpu: [], memory: [], disk: [] }); async function loadData() { const res await request.get(/history); servers.value res.data; // 默认展示第一台服务器的趋势 if (servers.value.length 0) { selectedServer.value servers.value[0].server; await loadTrend(selectedServer.value); } } async function loadTrend(serverName) { const res await request.get(/history/${serverName}/trend); trend.value res.data; } async function check() { await request.post(/check); alert(巡检任务已启动); } onMounted(() { loadData(); }); /script指标历史趋势显示效果新增了什么selectedServer记录当前查看哪台服务器的趋势默认取第一台trend保存趋势数据包含time、cpu、memory、disk四个数组loadTrend()单独封装一个函数拉取趋势数据await保证拿到数据后再往下走v-if / v-else有数据才渲染图表否则显示提示文案避免页面上出现三张空图。 注意loadTrend里用的是模板字符串反引号/history/${serverName}/trend会把变量名替换成实际值比字符串拼接/history/ serverName /trend清爽得多。12.常见问题排查配置过程中遇到问题先对照这里排查能省下大量搜索时间现象原因与解决办法npm run dev提示端口被占用5173 端口被其他程序占用终端输入y让 Vite 自动换一个端口或修改vite.config.js指定 port页面能打开但数据加载不出来后端没启动或后端启动报错。先看终端里 uvicorn 是否正常运行再访问 8000 端口的 /docs 验证接口控制台报 CORS policy 错误检查allow_origins是否包含你实际访问的地址——用127.0.0.1:5173打开页面就必须在列表里有它写localhost不算同一个源报401/403检查 Gitee 仓库是否开源、Token 是否正确这是上一章图床的坑别混淆报422 Unprocessable Entity接口参数不合法比如趋势接口的days传了 0 或 100超出 1~90 范围ECharts 图表一片空白① 容器 div 没设置高度必须有明确的 height② 数据还没请求回来就初始化了需要 watch③ 父容器display:none时初始化会导致尺寸为 0提示组件找不到 / 编译报错检查组件名拼写与文件名、import 路径是否一致components别写成component路径区分大小写改了代码页面没变化Vite 是热更新的若没生效检查终端是否报错或手动刷新浏览器CtrlF5 强制刷新npm install卡住不动切换国内镜像npm config set registry https://registry.npmmirror.com巡检后页面数据不刷新当前是手动刷新。可以在check()里加setTimeout(() loadData(), 3000)等巡检完成后自动重载图片在博客里显示不出来本文图片用的是本地绝对路径发布到博客前需要先上传图床可参考本系列的图床搭建章节13.本章小结回顾这一章我们完成了平台从后端到全栈的关键一步搭建 Vue3 项目Vite ↓ 封装 Axios打通前后端通信 ↓ 后端开启 CORS解决跨域 ↓ 开发 Dashboard 首页 ServerCard 卡片组件 ↓ 新增趋势接口 ECharts 可视化现在我们的巡检平台已经是一个真正可用的前后端分离系统了打开浏览器就能看到所有服务器的状态点一下按钮就能触发巡检还能看到 CPU、内存、磁盘的历史走势。阶段性成果从命令行脚本 → “后端 API 服务” → “可视化管理平台”这条路径也是很多企业内部工具的标准演进路线。下一步可以继续演进的方向我们将进入另一个大版本接入企业真实使用的监控方式更加贴近企业实际情况接入Prometheus基础监控实时采集服务服务器的监控 数据Grafana可视化将采集的数据以大盘形式呈现AlertManager告警到达阈值后触发告警SRE自动化平台融合融入现在的平台。如果你在跟着做的过程中遇到了本文没覆盖的问题欢迎在评论区留言交流。觉得有帮助的话点个赞收藏一下吧
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑