十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Django Vue前后端分离项目实战:环境搭建到联调完整指南

Django Vue前后端分离项目实战:环境搭建到联调完整指南 1. 从零搭一套能落地的前端工程前段时间接到一个网络设备运维系统的项目后端选了 Django前端准备用 Vue 做前后端分离。这类系统在政企、IDC、园区网场景里太常见了核心是设备台账、状态监控、告警工单、配置备份这些模块。选前后端分离不是因为赶时髦而是运维后台要同时面对网管人员、值班员、管理员几种角色页面交互多、状态更新频繁用传统 Django 模板硬憋会很难受。真正动手写代码之前前端工程的环境搭建往往是第一个坑。Node 版本不对、依赖装不上、跨域代理没配好、token 失效处理不完善这些问题我在好几个项目里都踩过而且几乎每个刚转前后端分离的团队都会来问一遍。这篇文章就把网络设备运维系统前端项目从选型到跑通联调的过程完整拆开包括每一步为什么这么选、命令怎么执行、报错怎么解决基本是按“每天要打开这个仓库干活”的标准来写的。如果你的手头正好要搞 Django Vue 的前后端分离项目或者准备用 Vue 3 做运维、监控、管理系统这一类偏后台的工具这篇可以直接当初始化手册用。已经跑过不少项目的朋友也可以重点看第 4 到第 6 章的 token 处理、代理转发和联调细节这些是最容易返工的地方。2. 技术选型与整体架构思路2.1 为什么是 Django 做后端、Vue 做前端网络设备运维系统这个业务有个特点数据模型稳定但关联复杂。设备、端口、IP、VLAN、告警、工单、用户权限彼此之间都是强关联Django 的 ORM 和 Admin 在这种场景下开发效率极高。尤其是设备台账这种需要大量列表筛选、关联查询的功能Django 的 queryset 一套组合拳下来比手写 SQL 省太多时间。前端选 Vue 则是看中它的生态和上手曲线。运维系统不是纯展示型网站有大量表单、表格、弹窗、实时状态刷新Vue 的响应式机制配合 Element Plus 这类组件库能把后端给的数据直接映射到界面上不需要像 jQuery 时代那样手动操作 DOM。Vue 3 的组合式 API 对复杂业务逻辑的复用也更友好比如设备状态轮询、告警推送这类逻辑可以封装成 hook多个页面共用。整个系统前后端通过 RESTful API 通信Django 侧用 djangorestframework 提供接口前端用 axios 发请求。前端工程独立维护、独立部署开发时走 Vite 代理解决跨域生产环境由 Nginx 统一托管静态文件和反向代理 API。这个架构在中小型运维系统里非常成熟团队里前后端人员可以并行开发互不阻塞。2.2 技术栈版本锁定先定版本再动手做前端环境搭建最忌讳的就是“最新版主义”。Vite、Vue、Node 的版本迭代很快新版本功能是香但第三方库的兼容性未必跟得上。这个项目的技术栈我直接锁死全部按经过验证的组合来Node.js 18 LTSVite 5 要求 Node 18但 Node 20 在某些旧版依赖上会有兼容问题18 最稳pnpm 8.x比 npm 安装速度快磁盘占用低锁文件统一Vue 3.4 Vite 5.xElement Plus 2.x网络设备运维系统里的表格、表单、树形控件它都有Pinia 2.x状态管理存用户信息、菜单权限、设备筛选条件Vue Router 4.xaxios 1.xunplugin-auto-import unplugin-vue-componentsElement Plus 按需自动导入避免全量打包Node 版本管理建议直接用 nvm-windowsWindows 环境或 nvmmacOS/Linux不要手动装。我见过太多人因为本机 Node 版本和项目要求不一致折腾一整天node_modules都装不干净换 nvm 之后一键切换省心非常多。注意Node 18 这个版本不是拍脑袋选的。用过 Node 20 跑 Vite 4 的同学可能遇到过Error: error:0308010C:digital envelope routines::unsupported其实就是 OpenSSL 3.0 改动导致的Node 18 是兼容性最稳妥的选择。2.3 前端项目目录设计按业务模块划分后端按 Django app 划分业务域device、alert、workorder、user 等前端目录也要跟后端业务形成映射否则联调时对齐接口会非常费劲。这个项目的src目录结构如下src/ ├── api/ # 接口请求定义按业务模块拆分文件 │ ├── device.js │ ├── alert.js │ ├── workorder.js │ └── auth.js ├── assets/ # 静态资源图片、全局样式等 ├── components/ # 通用组件分页表格、状态标签、表单弹窗等 ├── composables/ # 组合式函数设备状态轮询、告警声音提醒等 ├── layout/ # 主布局侧边栏、顶部栏、标签页 ├── router/ # 路由配置 ├── stores/ # Pinia 状态管理 ├── utils/ # 工具函数axios 实例、格式化、权限校验等 ├── views/ # 页面级组件 │ ├── dashboard/ │ ├── device/ │ ├── alert/ │ └── workorder/ ├── App.vue └── main.js为什么强调按业务模块分api目录而不是一个文件收所有接口网络设备运维系统的接口数量很容易膨胀一个设备模块就可能有十几个接口。全部堆在api/index.js里到了后期找接口、改参数、排查问题都像大海捞针。按模块拆开device.js只管设备模块的接口后端 Django 的 url 路由也是device/xxx这种前缀一一对应谁维护谁看都清晰。3. 本地开发环境准备3.1 Node 环境与包管理器安装这方面网上教程很多确实也是一路next就能解决的问题但有三个细节我吃过亏第一安装 nvm 之前把本机原有的 Node 彻底卸载干净否则 nvm 切换版本可能不生效。第二npm 镜像源建议全局设置成阿里源国内下载依赖速度差别巨大。第三锁定包管理器项目根目录放一个package.json的锁文件不要混用 npm 和 pnpm。# 安装 nvm 后执行 nvm install 18.20.4 nvm use 18.20.4 # 全局安装 pnpm npm install -g pnpm8.15.9 # 设置镜像源二选一 npm config set registry https://registry.npmmirror.com pnpm config set registry https://registry.npmmirror.com # 验证 node -v # v18.20.4 pnpm -v # 8.15.9装完 Node 之后建议顺手把pnpm的 store 目录指定到非系统盘默认是在 C 盘用户目录下项目多了会占好几个 G。可以在用户目录的.npmrc里配store-dirD:\.pnpm-store3.2 IDE 与插件配置前端开发 IDE 我推荐 VS Code免费、插件生态好、团队协作也方便。有几个插件是这个项目必须装的VolarVue 官方插件Vue 3 单文件组件的语法高亮、类型提示、模板表达式检查全靠它。注意 Vetur 是 Vue 2 时代的产物Vue 3 项目就别装了会冲突。ESLint统一代码规范配合项目里的.eslintrc配置保存时自动修复格式问题。Prettier代码格式化和 ESLint 配合使用不要让它俩规则打架。Path Intellisense文件路径提示引入组件和封装工具函数时很省时间。VS Code 的settings.json里我习惯做两个配置{ editor.formatOnSave: true, editor.codeActionsOnSave: { source.fixAll.eslint: true } }这样每次保存文件ESLint 自动修一遍代码风格Prettier 自动排一遍版团队里不管谁写出来的代码风格都是一致的code review 的时候不会为了缩进和引号浪费口舌。3.3 Git 仓库初始化与提交规范前端工程初始化完之后第一件事是git init提交一个干净的基线版本这之后再做任何配置改动都容易对比和回滚。.gitignore必须把node_modules、dist、.env.local这种环境相关文件排除掉这里有个典型误区.env.development和.env.production是可以提交到仓库的公共配置但.env.local是本机私有的比如本地调试时连到测试服务器的地址一定不能提交。我见过有人把测试数据库的 IP 和账号密码不小心推到代码仓库后面整个内网被扫的风险大增这种事一次都不能发生。Git commit 信息建议统一用feat(module): description这种 Conventional Commits 格式比如feat(device): add device list page。前期养成习惯后面生成 changelog 和排查 bug 定位到具体提交都会轻松很多。4. 创建 Vue 3 项目并接入 UI 框架4.1 用 Vite 创建项目命令与参数详解Vite 现在创建项目的方式很成熟不需要用vue-cli一条命令直接搞定。这里我用的是create-vite的 Vue TypeScript 模板pnpm create vite network-ops-frontend --template vue-ts cd network-ops-frontend pnpm install这里有个选型细节为什么要用 TypeScript 而不是纯 JavaScript网络设备运维系统里设备数据、告警数据的字段结构非常固定像设备 IP、设备类型、状态、所属站点用 TS 的 interface 定义好之后编辑器会给出精确的字段提示写错字段名直接在编辑器里就报红了。这相当于在后端 Django 的 serializer 之外又给前端加了一层静态检查联调时字段对不齐的概率大幅降低。项目跑起来之后第一件事是清理模板自带的演示代码。src/components/HelloWorld.vue删掉App.vue改成空白布局src/style.css保留基础样式但清掉模板样式。这一步看起来没有技术含量但很多人偷懒不管后面写页面时总会被这些模板残留干扰。4.2 Element Plus 接入与自动按需导入Element Plus 是这个系统的主要 UI 组件库我用了它的自动按需导入方案。它的原理是借助unplugin-auto-import和unplugin-vue-components两个 Vite 插件在编译时自动把代码里用到的组件和 API 导入避免全量打包把没用到的组件也塞进产物里。vite.config.ts配置如下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()] }) ] })这样在.vue文件里直接写el-table、el-form就能用不需要手写import { ElTable } from element-plus。用ElMessage这种命令式 API 的时候要注意auto-import 配置里需要额外加imports: [vue, vue-router, pinia]否则编辑器里直接写ref、computed、useRouter会提示找不到。重点按需导入虽然好用但样式文件的引入也要跟着走。如果发现组件能用但样式不对检查一下是不是漏了ElementPlusResolver({ importStyle: css })这个配置或者手动在main.ts引入element-plus/dist/index.css做兜底。4.3 路由与布局的初步搭建运维系统的界面布局基本是固定的套路左侧侧边栏、顶部导航、中间内容区。我直接用vue-router 一个主布局组件把这套骨架定义好// router/index.ts import { createRouter, createWebHistory } from vue-router const router createRouter({ history: createWebHistory(), routes: [ { path: /login, component: () import(/views/login/index.vue) }, { path: /, component: () import(/layout/index.vue), redirect: /dashboard, children: [ { path: dashboard, name: Dashboard, component: () import(/views/dashboard/index.vue), meta: { title: 运行总览 } }, { path: device/list, name: DeviceList, component: () import(/views/device/list.vue), meta: { title: 设备管理 } } // ... 其他模块 ] } ] })路由用懒加载() import(...)而不是直接静态引入这样打包时每个页面会单独拆成一个 chunk首屏只加载当前需要的文件。运维系统页面多不懒加载的话首屏 JS 动不动就几 MB加载特别慢。布局组件layout/index.vue这里不展开写完整代码思路是左侧el-menu绑定路由的meta.title和path右侧内容区放router-view /。侧边栏菜单最好用路由表自动生成不要手写两套不然加一个页面要改两个地方忘改就会 404。5. 网络设备运维系统的前端工程化配置5.1 开发服务器配置跨域代理前后端分离开发中前端跑在localhost:5173Django 跑在localhost:8000浏览器会有跨域限制。正经的解决方案不是在后端开 CORS 放行当然 Django 那边可以配django-cors-headers方便调试而是让 Vite 开发服务器做代理。vite.config.ts里加server.proxyexport default defineConfig({ server: { host: 0.0.0.0, port: 5173, proxy: { /api: { target: http://localhost:8000, changeOrigin: true } } } })关键在于changeOrigin: true它会把请求头的Host字段改成目标地址Django 那边看到的请求就是来自localhost:8000不会因为 Host 不匹配被拒。这样前端调用时统一走/api/device/list这种相对路径开发环境由 Vite 转给 Django生产环境由 Nginx 转给 Django 的 uWSGI/Gunicorn前端代码里不需要关心后端到底部署在哪台服务器。5.2 环境变量管理区分开发、测试、生产网络设备运维系统一般至少有三套环境本地开发、测试环境、生产环境。不同环境的后端接口地址、是否开启 mock、日志级别都不一样这些必须用环境变量区分。Vite 的环境变量机制是基于.env文件的以VITE_开头命名的变量会被暴露到前端代码# .env.development VITE_API_BASE_URL/api VITE_USE_MOCKfalse # .env.production VITE_API_BASE_URL/api VITE_USE_MOCKfalse在代码里这样使用// utils/request.ts const baseURL import.meta.env.VITE_API_BASE_URL || /api注意生产环境通常也是/api因为 Nginx 会把/api反向代理到 Django 服务写成相对路径可以做到“前端代码不感知后端地址”。只有特殊情况比如调试时直接连测试服才在.env.local里覆盖成http://10.0.0.5:8000/api。5.3 axios 二次封装与请求拦截axios 不能直接用必须封装成一个统一的请求实例把 baseURL、超时时间、请求头、响应拦截、错误处理全部集中起来。这样任何一个页面发请求都能自动带上 token响应异常了也不用每个页面都写一遍错误处理。src/utils/request.ts的核心逻辑import axios from axios import { ElMessage } from element-plus import { useUserStore } from /stores/user import router from /router const service axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL || /api, timeout: 15000 }) // 请求拦截器自动附带 token service.interceptors.request.use((config) { const userStore useUserStore() if (userStore.token) { config.headers.Authorization Bearer ${userStore.token} } return config }) // 响应拦截器统一处理错误 service.interceptors.response.use( (response) response.data, (error) { if (error.response?.status 401) { const userStore useUserStore() userStore.resetToken() router.push(/login) ElMessage.error(登录状态已过期请重新登录) } else if (error.response?.status 403) { ElMessage.error(没有权限执行此操作) } else { ElMessage.error(error.response?.data?.detail || 请求失败请稍后重试) } return Promise.reject(error) } ) export default serviceDjango 那边用的认证方式是 djangorestframework-simplejwt后端返回的 token 类型是Bearer token所以请求头格式要跟它对齐。这个细节错了很容易踩坑后端明明校验通过但前端一直 401就是Authorization头格式的问题。提示axios 的响应拦截器我是直接返回response.data而不是返回response。这样业务代码里const data await getDeviceList()拿到的直接是后端返回的业务数据不需要每处都写res.data.data代码会干净很多。5.4 Token 存储与刷新机制网络设备运维系统要求用户登录后长时间保持会话access token 过期后需要自动刷新不能用“过期就让用户重新登录”这种粗暴方案。JWT 的 access token 有效期一般设 30 分钟到 2 小时refresh token 有效期设 1 到 7 天。前端 token 存储我推荐放在 Pinia localStorage 里。Pinia 负责运行时读取localStorage 负责持久化这样刷新页面后登录状态不丢// stores/user.ts import { defineStore } from pinia export const useUserStore defineStore(user, { state: () ({ token: localStorage.getItem(access_token) || , refreshToken: localStorage.getItem(refresh_token) || , userInfo: null }), actions: { setToken(accessToken: string, refreshToken: string) { this.token accessToken this.refreshToken refreshToken localStorage.setItem(access_token, accessToken) localStorage.setItem(refresh_token, refreshToken) }, resetToken() { this.token this.refreshToken localStorage.removeItem(access_token) localStorage.removeItem(refresh_token) } } })自动刷新 token 我封装成一个方法在响应拦截器里遇到 401 时调用// utils/refreshToken.ts import request from ./request import { useUserStore } from /stores/user let isRefreshing false let pendingQueue: Array(token: string) void [] export async function refreshToken() { const userStore useUserStore() if (isRefreshing) { // 如果已经在刷新中返回一个 Promise让其他请求排队等待 return new Promise((resolve, reject) { pendingQueue.push((token) { userStore.token token resolve(token) }) }) } isRefreshing true try { const res: any await request.post(/auth/refresh/, { refresh: userStore.refreshToken }) const newToken res.access userStore.setToken(newToken, userStore.refreshToken) pendingQueue.forEach((cb) cb(newToken)) pendingQueue [] return newToken } catch (e) { pendingQueue [] userStore.resetToken() window.location.href /login throw e } finally { isRefreshing false } }这段代码里最容易忽略的是“刷新请求的并发去重”。假设页面上同时有 10 个请求都因为 token 过期返回 401如果不加isRefreshing判断这 10 个请求会同时发起刷新 token 的请求Django 那边收到十次 refresh 请求浪费资源不说还可能因为 refresh token 被重复使用而失效。加上这个队列机制只有第一个请求真正去刷新其余 9 个排队等新 token刷新完成后统一重放。注意响应拦截器里重放请求时要记得用service(originalConfig)而不是直接request(originalConfig)否则会再次进入拦截器死循环。5.5 Pinia 状态管理不只是存 tokenPinia 在这个系统里的职责不止 token还承担用户信息、侧边栏折叠状态、设备列表筛选条件这些全局状态的维护。重点提一下“设备筛选条件的持久化”运维人员经常在设备列表页筛选“某站点 某型号 告警状态”翻页查看然后不小心刷新页面条件全没了重新筛一遍挺烦的。用 Pinia 存一份筛选条件并同步到 localStorage// stores/deviceFilter.ts export const useDeviceFilterStore defineStore(deviceFilter, { state: () ({ filterParams: JSON.parse(localStorage.getItem(device_filter) || {}) }), actions: { setFilterParams(params: any) { this.filterParams params localStorage.setItem(device_filter, JSON.stringify(params)) } } })页面在onMounted时从 store 里读回条件回填到搜索表单再发起查询。这个功能虽小但在真实使用中用户感知特别强属于低成本高收益的体验优化。6. 与 Django 后端联调的关键细节6.1 登录认证流程对接Django 那边用 djangorestframework-simplejwt 提供登录接口通常是POST /api/auth/login/请求体是{ username: ..., password: ... }返回{ access: ..., refresh: ... }两组 token。前端登录页拿到后直接存进 user store。有个细节要注意登录成功后前端会再调一个GET /api/auth/profile/获取当前用户信息用户名、角色、权限列表。这个接口的响应里如果有角色和权限信息就要存起来因为后面做按钮级权限控制要用。比如普通运维人员看不到“删除设备”按钮网络管理员才能看到这时候前端要根据权限字段做判断而不是简单地把按钮隐藏了事——后端同样要做校验前端只是改善交互体验。6.2 用户路由守卫的实现vue-router的全局前置守卫有两个职责如果还没登录所有页面都拦下来跳去登录页如果已登录但又访问登录页就直接跳回首页。实现思路router.beforeEach((to, from, next) { const userStore useUserStore() if (to.path /login) { if (userStore.token) { next(/) } else { next() } } else { if (!userStore.token) { next(/login?redirect${to.fullPath}) } else { next() } } })这里redirect参数很重要用户被拦截去登录之后登录成功应该自动跳回原来想访问的页面而不是固定跳首页。网管人员在值班处理告警时刷新页面 token 还在但如果 token 刚好过期被踢到登录页登录后还要重新找那个告警页面就很恼火。有redirect参数体验会好很多。6.3 接口字段命名风格的统一Django 后端默认是 snake_casedevice_type、ip_address前端 JavaScript 习惯用 camelCasedeviceType、ipAddress。前后端联调时最烦的就是字段名对不上。这个问题我的做法是后端 Django 的 serializer 里直接定义好字段名让前端不用做任何转换。比如class DeviceSerializer(serializers.ModelSerializer): device_type serializers.CharField(sourceget_device_type_display) class Meta: model Device fields [id, name, device_type, ip_address, status, site_name]只要这个接口是做给前端用的字段命名就以“前端最方便使用”为标准来定义两边的字段名完全一致省掉前端做 map 转换的功夫。当然这里需要在项目初期和后端、前端约定好否则后面改字段名牵连太大。设备状态这种枚举字段后端返回的应该是人类可读的字符串如online、offline、warning由前端用一个映射表转成中文标签和颜色而不是后端直接返回“在线”“离线”这种中文否则做国际化或者状态颜色标识时会很被动。6.4 Mock 数据方案的临时替代如果后端接口还没开发完前端可以先不等。Vite 支持在本地 mock 接口虽然不是正式方案但联调前期很实用。最简单的方式是用vite-plugin-mock插件或者直接在 vite.config 里配一个本地插件// vite.config.ts 中配置 mock 插件开发阶段使用 import type { Plugin } from vite function mockPlugin(): Plugin { return { name: mock-dev-server, configureServer(server) { server.middlewares.use(/api/device/list, (req, res) { res.setHeader(Content-Type, application/json) res.end(JSON.stringify({ code: 0, data: { total: 2, items: [ { id: 1, name: 核心交换机-01, ip_address: 192.168.1.1, status: online }, { id: 2, name: 路由器-01, ip_address: 192.168.1.2, status: warning } ] } })) }) } } }发布到生产环境时这个插件不生效所以不会影响正式代码。前端在 mock 阶段把页面写出来后端接口就绪后只需要关掉 mock切换到真实请求整个过程接口签名不变验证会很顺畅。7. 常见问题与排查技巧实录7.1 依赖安装失败的排查路径前端项目环境搭建中“跑不起来”有七成是依赖安装环节出的问题。典型报错和对应解法如下报错场景原因解决方案ERR_OSSL_EVP_UNSUPPORTEDNode 版本过高OpenSSL 兼容问题切换到 Node 18 LTSETIMEDOUT/ECONNRESET网络原因无法连接到 registry配置阿里镜像源或重试pnpm installpeerDependencies冲突依赖之间的版本要求不匹配尝试pnpm install --force或手动升级对应依赖Cannot find module node-sass项目用了 sass 但只装了 node-sass 或版本不兼容统一换成sassDart Sass不再用 node-sassvite 不是内部或外部命令依赖未正确安装或 pnpm 脚本环境异常先执行pnpm install然后用pnpm run dev而非直接vite其中node-sass这个坑我重点提醒一下。node-sass 是 LibSass 的 Node 封装早已停止维护新 Node 版本装它几乎必报错。如果你看到项目里有人还在用 node-sass建议直接换成sassDart SassAPI 基本兼容安装速度还更快。7.2 跨域与代理问题的自我检查清单前后端分离项目里跨域问题是聊天记录里出现频率最高的问题之一。遇到前端请求报 CORS 错误按这个顺序排查确认请求是不是走了 Vite 代理浏览器 F12 看 Network如果请求 URL 是http://localhost:5173/api/device/list说明走了代理。如果显示http://localhost:8000/api/device/list说明没走 Vite 代理可能是 baseURL 写成了绝对地址。检查 target 地址对不对Django 到底跑在localhost:8000还是127.0.0.1:8000Django 启动时要留意runserver 0.0.0.0:8000和127.0.0.1:8000的监听范围不一样。检查 Django 是否开了 CORS即使有 Vite 代理如果前端直接访问后端比如某些上传接口走的是 CDN 地址后端要安装django-cors-headers在settings.py里配置CORS_ALLOWED_ORIGINS。看后端日志Django 终端有没有打印收到请求的记录如果收到了说明请求到达了后端问题大概率在响应阶段而不是代理阶段。经验遇到跨域问题先别急着看前端代码先确认“后端有没有收到请求”。这一步能快速把问题分成两类代理没配好 vs 后端响应有问题排查效率会高很多。7.3 接口 401 循环跳转问题的处理这个bug在前后端分离项目里非常常见token 失效后某个接口返回 401响应拦截器跳转到登录页登录页又发了一个请求去获取用户信息这个请求如果也带上了无效 token又 401然后跳转到登录页……于是页面疯狂刷新或者卡在登录页出不来。解法是先判断当前是不是已经在登录页只有不在登录页时才跳转并提示。在拦截器里加一层判断即可if (error.response?.status 401) { if (router.currentRoute.value.path ! /login) { try { // 先尝试用 refreshToken 刷新 await refreshToken() // 刷新成功后重放原请求 return service(error.config) } catch { const userStore useUserStore() userStore.resetToken() router.push(/login) ElMessage.error(登录状态已过期请重新登录) } } else { // 登录页本身的接口异常不跳转只提示 ElMessage.error(用户名或密码错误) } }这里还涉及一个细节刷新 token 之后原始请求要重新发一次。之前提到过error.config就是 axios 保存的原始请求配置重放时用service(error.config)即可。注意要在刷新成功后从 store 里重新读取新 token因为这时候 store 里的 token 已经是新的了。7.4 HMR 失效与页面白屏的排查思路开发模式下改代码页面自动更新这是 Vite 的 HMRHot Module Replacement功能。如果出现 HMR 失效通常原因有这几个当前编辑的文件被多个组件依赖Vite 无法精确热更新只能整页刷新。这种情况重新刷新一次页面就好不在代码层面处理。项目中resolve.alias配置了指向src但 Vite 没识别到这个配置。检查vite.config.ts里是否配了resolve.alias。使用的组件库或业务代码里有不受支持的写法比如模块顶层直接修改module.exports之类的 CommonJS 写法。新写的代码尽量用 ESM 规范。白屏问题一般是 JS 运行报错。打开浏览器 F12 看 Console 面板最常见的有这几种- Uncaught TypeError: Cannot read properties of undefined (reading xxx) - Uncaught ReferenceError: xxx is not defined第一个通常是后端返回的数据结构和前端预期不一致比如接口返回{ data: [] }但前端写的是res.items。第二个是变量名拼写错误或者忘记 import。遇到白屏不要慌Console 一定有报错按错误提示定位通常很快。8. 项目启动后的调优与扩展方向前端环境搭建完成、登录流程跑通之后这个项目已经有继续生长的骨架了。从环境搭建角度还有几件事是值得顺手做掉的第一接入unplugin-icons和图标集。运维系统里设备状态、告警级别、操作按钮都需要小图标手动引入 SVG 太繁琐unplugin-icons可以自动按需加载iconify/json里的图标用i-ep-warning /这样的标签直接渲染代码量少很多。第二配置unplugin-html或手动调整index.html的标题、favicon 和 SEO meta。运维系统虽然是后台系统但浏览器的页面标题左上角标签如果不能动态变化多标签页开多了会很难区分。可以在路由afterEach里根据meta.title动态设置document.title。第三给构建产物加上gzip或brotli压缩。生产环境 Nginx 配置了gzip on时Vite 构建时再用vite-plugin-compression预生成.gz文件服务器直接把预压缩文件下发省掉 Nginx 实时压缩的 CPU 消耗首屏速度也会有明显提升。第四如果项目规模继续扩大考虑接入Vitest做单元测试优先覆盖工具函数比如 token 刷新逻辑、权限判断逻辑和 API 封装。这些是项目里最容易出回归问题的部分测试成本比页面组件低收益却高得多。我还想提醒一点环境搭建不是一次性的工作。Node 和依赖版本会持续迭代新成员加入团队时需要一份简明的 README把 Node 版本、包管理器、启动命令、环境变量说明、常见报错都写进去。这份文档花 20 分钟写能省下后面无数次“帮我看一下为什么跑不起来”的时间。网络设备运维系统这类项目前端环境搭好之后真正的硬仗在业务页面和数据交互但地基打不牢后面每走一步都在还债。希望这篇文章能帮你把这个地基一次打好后续专注在设备管理、告警监控这些真正的业务价值上。
返回列表