ARTICLE DETAIL

资讯详情

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

Vue项目实战:从环境配置到打包SpringBoot的HTML与JS核心细节

Vue项目实战:从环境配置到打包SpringBoot的HTML与JS核心细节 接手Vue项目三年我最大的体会是决定项目上限的不是Vue API背得多熟而是HTML和JavaScript这两块基本功。模板语法写到最后还是操作DOM响应式数据追到底还是JavaScript的代理和调用栈。这次不聊虚的我从环境配置、路由传参、m3u8播放、打包进SpringBoot这些真实部署过的场景出发把Vue项目里真正用得到的HTML和JavaScript细节一次讲清楚。刚开始学Vue的朋友可以直接照着抄作业写了两三年项目的人也可以把这份内容当成排查清单和面试复习提纲来用。1. 环境配置是第一道坎装好、跑通、交付三步走很多新手在Vue官网逛了一圈转头就被环境配置劝退了。其实这一环没有太多技术含量全是体力活但步骤错了会让你怀疑人生。我帮同事处理过的环境问题十个里有八个是版本不对、镜像混乱、依赖没装全这三类。把这些前置动作理顺后面写代码的心情会完全不一样。1.1 Vue安装与环境配置的完整流程先说结论现在新建Vue项目默认选Vite不要再用老的vue-cliwebpack初始化了。Vite的启动速度和热更新比webpack舒服太多Vue 3的官方模板也是基于Vite。前提是电脑上装好Node.js建议直接用LTS版本我用的是Node 18/20系列尽量不要追非LTS的奇数版本社区踩坑记录一大半都是Node版本惹的祸。node -v npm config set registry https://registry.npmmirror.com npm create vitelatest my-vue-app -- --template vue cd my-vue-app npm install npm run dev这里有个细节容易被忽略npm create vite后面那串-- --template vue是指定生成Vue模板。如果你装的版本比较旧交互界面会让你选框架选Vue、选JavaScript或者TypeScript看团队要求就行。安装依赖之前建议先换镜像源不然大型依赖树下载起来非常痛苦。实测在默认源下装一个带element-plus、axios、vue-router、pinia的项目可能要十分钟往上换了镜像基本一分钟内搞定。跑起来之后浏览器打开 http://localhost:5173 看到Vue的欢迎页说明环境没问题。这时可以先看一眼package.json里的scriptsdev对应开发环境build是构建产物preview可以本地预览构建结果。这三个命令在项目交付和打包排查时天天用得到。另外如果你习惯用IDEA开发Vue项目装好Vue.js插件、Prettier插件就行本质还是命令行那套流程IDE只是帮你把命令封装成了按钮。前后端联调阶段还要在vite.config.js里配好开发代理。最常见的写法是把/api的请求代理到后端服务地址这样本地开发时前端页面和后端接口就同源了绕开了浏览器跨域限制。这个代理只在dev环境生效打包后走的是后端真实同源部署很多人忘了这一点导致本地调得好好的、一上线就跨域报错。1.2 依赖安装报错Failed to load tsconfig 这类问题的根源项目跑起来之后第一波坑往往出现在依赖安装和类型检查上。热搜里那条failed to load tsconfig vue/tsconfig/tsconfig.web.json: tsconfig not found就是典型。这个报错通常出现在两类场景一是你用npm create vue创建了TS模板项目但node_modules没装全就跑命令二是从同事手里拿到源码只拷贝了业务代码package.json里devDependencies的vue/tsconfig被漏掉了。tsconfig.json里 extends 指向 vue/tsconfig/tsconfig.web.json实际上是在继承一个npm包里的类型配置跟你写extends ./other.json一个道理只是路径指向了node_modules。报错说tsconfig not found本质就是包没装。解决方式很简单npm install -D vue/tsconfig npm install经验之谈凡是从别人那里接手项目第一反应不是全局搜索代码而是先跑npm install。node_modules残缺导致的诡异报错占我排查记录的六成以上。另外package-lock.json必须提交到版本库它锁定了依赖版本是复现环境的唯一可靠依据。没有这个文件同一个package.json在不同时间装出来的依赖树都可能不一样。1.3 项目源码交付怎么发给别人才能一次跑通热搜里那条“vue项目源码怎么发给别人”我估计很多人踩过坑。最推荐的交付方式不是压缩整个文件夹而是走Git仓库加一份环境说明。压缩包方式也不是不行但一定要处理几件事删掉node_modules、删掉dist、保留package.json和package-lock.json、附上README。README里写清Node版本、镜像源、安装步骤、启动命令、构建命令以及有没有后端接口代理配置。注意发给别人的源码里如果包含后端地址、密钥、地图token记得检查一下是否泄露。地图token、接口地址这类敏感信息应该放进.env.local并加入.gitignore千万别提交到Git历史里不然清都清不干净。如果对方打开项目发现启动报错按这个顺序排查先确认Node版本再跑npm install然后看npm run dev的输出。八成问题是依赖没装完整或者本地端口被占用。这是我最常用的三连排查基本能覆盖掉所有交接场景。2. HTML不是配角模板语法背后还是DOM思维很多人觉得Vue里写的是模板语言跟HTML没关系了。实际上浏览器最终渲染的还是DOMVue只是把模板编译成render函数再生成虚拟DOM。所以HTML规范里那些东西在Vue项目里一样起作用DOCTYPE声明、lang属性、meta charset这些决定了页面渲染模式、语言和字符集。热搜里反复出现!doctype html html langzh-cn head meta charsetutf-8其实就是标准HTML5骨架Vue项目的index.html里也是长这样。2.1 从DOCTYPE到根节点Vue模板与HTML的映射关系Vue 3的组件根节点可以是多个元素了Vue 2里强制单根节点这个变化直接改变了布局写法的自由度。不过就算多根节点模板编译器也会把它们包进Fragment最终DOM树还是规范的。另外v-html这个指令要特别小心它会把一段HTML字符串直接渲染进DOM跟innerHTML一样存在XSS风险。凡是用户输入内容绝对不要用v-html去渲染。class绑定、style绑定的响应式写法本质还是JavaScript在操作样式。数组、对象、字符串三种绑定方式对应不同场景数组适合多条件叠加class对象适合开关式样式普通字符串适合常量。这个映射关系想清楚模板代码能清晰很多。比如:class[isActive active, base]这种写法比在模板里堆三元表达式可读性强多了。2.2 网页结构基础在Vue里的落地场景做Vue项目照样需要网页制作的基本功。语义化标签header、main、section、footer在组件化之后依然有意义只是粒度从整页变成了组件内部。表单是另一块重头HTML原生的form、label、input属性在组件封装里经常被忽略但可访问性就是靠这些细节堆出来的label的for属性要对应input的idrequired和disabled的状态要在原生控件上同步。Vue在这里给了v-model帮你省去绑定工作但表单校验还是得理解原生事件冒泡机制submit事件发生在form上而不是button上这个基础不搞清楚表单校验你永远写不对。顺带说一个冷门需求html邮件。有些项目要做邮件模板Vue页面转邮件时不能用组件、不能用外链样式因为邮件客户端只认内联样式和Table布局。这时候你之前的HTML基础反而比Vue知识更管用。我的做法是单独维护一套纯HTML邮件模板用字符串拼接数据再用手写模板引擎渲染Vue这套体系反而不适合邮件场景。2.3 一键返回顶部一个典型的DOM操作算法题热搜里的“html一键返回顶部算法”很有代表性它考察的就是最基础的DOM操作和scroll机制。实现分三步监听滚动事件、判断当前位置、执行平滑滚动。const showTopBtn ref(false) function handleScroll() { showTopBtn.value window.scrollY 300 } function backToTop() { window.scrollTo({ top: 0, behavior: smooth }) } onMounted(() window.addEventListener(scroll, handleScroll)) onUnmounted(() window.removeEventListener(scroll, handleScroll))模板里一个buttonv-if控制显示隐藏click触发backToTop完事。这里有几个细节监听scroll的目的是判断是否出现返回按钮300px那个阈值可以按需求调平滑滚动优先用scrollTo配behavior: smooth比手动写requestAnimationFrame逐帧滚动靠谱需要兼容老浏览器的项目可以降级改成document.documentElement.scrollTop 0。记得在onUnmounted里移除监听不然组件销毁后监听器还在页面滚动一多就会触发回调性能会很难看。我见过一个上线项目就是因为忘了移除scroll监听页面切了几十个组件之后滚动明显掉帧。3. JavaScript基本功决定组件质量类型、函数、数值与报错排查Vue组件写得漂不漂亮很大程度上取决于JavaScript功底而不是Vue语法背得熟不熟。数据校验、逻辑封装、异常捕获这些才是组件健壮性的来源。下面这几个点都是热搜高频词也是我日常Review代码时看得最多的部分。3.1 数据类型判断typeof之外你必须会的两招“javascript判断数据类型”是面试题也是实战题。typeof能判断基础类型但null和数组会翻车typeof null返回objecttypeof []也返回object。所以判断类型不能只靠typeof。正规做法是Object.prototype.toString.call它返回[object Array]、[object Object]这类带标签的字符串再配合一个通用函数取类型名。function getType(value) { return Object.prototype.toString.call(value).slice(8, -1) } console.log(getType(null)) // Null console.log(getType([1, 2])) // Array console.log(getType(new Date())) // Date数组还要单独记一个Array.isArray它比getType更语义化而且不受跨iframe影响。我见过不少老代码用value instanceof Array判断数组跨了iframe或window上下文就失效这是个隐蔽坑。表格放这儿方便记忆判断目标typeof结果正确姿势nullobject直接 value null数组objectArray.isArray(value)普通对象objectObject.prototype.toString.call(value) [object Object]函数functiontypeof value function这个知识点在Vue里最常用的场景是表单组件。父组件可能给你传字符串、数字、数组传值校验靠的就是这套类型判断。写通用组件时我习惯先写好一个isObject、isArray、isFunction的工具集比到处写typeof干净太多。3.2 函数封装与保留两位小数的精度陷阱“javascript保留两位小数”听起来很简单但toFixed有精度问题。最典型的例子是1.005.toFixed(2)在多数浏览器引擎里结果是1.00而不是1.01因为1.005在JavaScript的二进制浮点数里存储值略小于1.005。金融、电商、报表场景遇到这种问题会直接暴雷。function roundToFixed(value, digits 2) { const factor Math.pow(10, digits) return Math.round((value Number.EPSILON) * factor) / factor }有金额计算需求的项目我强烈建议用上面这种方式再转字符串不要裸用toFixed。如果只是展示层做千分位格式化可以用Intl.NumberFormat它同时处理小数位把计算逻辑和展示逻辑分开写。函数封装这块Vue组件里的公共逻辑一定要抽出来防抖节流是最高频的两个工具函数。滚动列表、搜索输入、窗口resize这三个场景基本都用得上。防抖的本质是延迟执行节流的本质是限制频率概念别搞混。顺手写个简单的防抖函数组件里直接引用export function debounce(fn, delay 300) { let timer null return function (...args) { if (timer) clearTimeout(timer) timer setTimeout(() fn.apply(this, args), delay) } }3.3 运行时报错的排查套路“javascript运行时报错”是前端工程师的日常。Vue项目里最常见的就是Cannot read properties of undefined (reading xxx)多半是接口返回的数据结构和预期不一致。排查套路我总结成三步先看报错堆栈定位到组件再看接口返回的原始JSON最后检查渲染表达式里有没有直接访问深层属性。如果数据还没加载出来就渲染要么用v-if判断要么用可选链?.要么给初始值。还有一类是事件处理函数的错误Vue默认会把错误抛到全局。想集中收集前端异常可以设置app.config.errorHandler把错误统一上报。如果你的项目用了onErrorCaptured钩子可以在组件边界捕获子孙组件的错误防止整棵组件树崩溃。开发阶段别关掉Vue的warn提示很多模板写法问题其实在warn阶段就暴露了等编译成production再排查就晚了。我把这两年项目里高频出现的报错整理成了速查表排查时直接对号入座报错现象常见原因处理方式Cannot read properties of undefined接口数据未返回就渲染给初始值、用可选链、加v-ifFailed to load tsconfigvue/tsconfig未安装npm i -D vue/tsconfigPort 5173 is already in use默认端口被占用换端口或kill占用进程npm ERR! ERESOLVE依赖版本冲突升级依赖或调整Node版本4. 路由、插槽与Canvas样式Vue的进阶拼图过了基础阶段真正让组件具备扩展能力的就是路由、插槽和样式作用域这几块。它们单独拿出来都不难难的是组合在一起时还保持结构清晰。这部分内容对应热搜里的vue路由、vue路由参数、vue动态路由、vue插槽、vue样式和canvas 2d vue一次讲全。4.1 路由参数与动态路由的传参方式Vue Router 4里组件内用useRoute和useRouter采集路由信息、执行跳转。传参方式有两类query和params。query拼在URL问号后面刷新不丢params如果对应的是动态路由段比如/detail/:id也一样不丢但如果params跟在router.push里却没有出现在path中刷新页面就会丢。这是新手最容易踩的坑。const router useRouter() const route useRoute() // 跳转并携带参数 router.push({ path: /detail/${id} }) // 在详情页读取 console.log(route.params.id)动态路由还有一个进阶用法接口返回权限菜单后用router.addRoute动态注册页面路由后台管理系统基本都会用到。注意addRoute要在路由准备阶段完成注册不然首次导航进来会找不到匹配路由。我在项目里是用一个全局路由守卫配合白名单列表处理的先判断目标路由是否已经注册没有就先addRoute再next({ ...to, replace: true })这样页面刷新后动态路由不会白屏。4.2 插槽在组件复用中的关键作用vue插槽是组件复用最重要的机制之一。默认插槽可以理解为“在组件内部预留一块HTML区域由父组件决定内容”具名插槽是把这个区域分成多个命名槽位作用域插槽则是子组件把内部数据暴露给父组件使用。表格组件是作用域插槽最典型的舞台子组件遍历数据把当前行数据通过插槽prop传给父组件父组件决定这一列显示什么。!-- 子组件表格 -- tbody tr v-forrow in rows :keyrow.id slot namerow :rowrow{{ row.name }}/slot /tr /tbody !-- 父组件使用 -- DataTable :rowslist template #row{ row } td{{ row.name }}/td td共 {{ row.count }} 条/td /template /DataTable这个写法让通用表格组件既能循环输出行结构又能让不同业务页面自定义单元格内容避免了每个页面重写表格。注意插槽作用域里的解构写法#row{ row }是Vue 3的语法糖老项目里写v-slot:rowprops也能用。我见过不少团队把表格组件写成了上百行的巨型组件就是因为没用插槽把各种业务分支全部堆在组件内部看着都疼。4.3 样式作用域与Canvas 2D的联动写法vue样式方面scoped是最常用的设计。它的原理是给组件元素加data-v-xxx属性再通过属性选择器限定样式作用范围。子组件根节点和父组件scoped样式的关系也经常让人困惑父组件能影响子组件根节点样式是设计好的行为想穿透修改子组件内部元素就得用:deep()。Canvas在Vue里的用法比较特殊因为Canvas不是响应式的DOM数据变化不会自动重绘。正确姿势是把canvas元素用ref拿到在onMounted里获取2D上下文然后手动绘制。const canvasRef ref(null) onMounted(() { const ctx canvasRef.value.getContext(2d) // 绘制之前清空画布 ctx.clearRect(0, 0, canvasRef.value.width, canvasRef.value.height) ctx.fillStyle #3b82f6 ctx.beginPath() ctx.arc(80, 80, 50, 0, Math.PI * 2) ctx.fill() }) onUnmounted(() { // 清理定时器、事件监听、动画帧 })如果你要做图表或绘制动画记得在onUnmounted里取消requestAnimationFrame循环否则组件销毁后Canvas还在后台跑性能问题会很隐蔽。另一个需要手动校准的场景是Canvas绘制模糊多数是因为Canvas元素的CSS尺寸和设备像素比不一致处理方式是按devicePixelRatio放大绘图尺寸。这个细节我调过一整个下午画出来的文字全是糊的最后发现是高分屏适配没做。5. 实战案例从m3u8视频到地图组件接入理论说再多不如来几个真实场景。这一节覆盖热搜里的vue播放m3u8免安装、用在vue里的腾讯地图、mapbox vue、html格式转换wps表格、html转为md这些高频需求都是我实际部署过的方案直接拿来改改就能用。5.1 Vue播放m3u8视频免安装的浏览器原生方案m3u8是HLSHTTP Live Streaming协议的播放列表文件里面存的是视频分片地址列表。Apple设备上Safari原生支持HLSvideo标签直接设置src就能播。但Chrome、Edge、Firefox在桌面端默认不支持HLS需要引入hls.js这个JavaScript库来解包播放。这就是“免安装”的意思不需要装任何播放器软件前端用代码就能解决。npm install hls.jsimport Hls from hls.js import { onMounted, ref } from vue const videoRef ref(null) const videoUrl https://example.com/live/stream.m3u8 onMounted(() { const video videoRef.value if (video.canPlayType(application/vnd.apple.mpegurl)) { // Safari 原生支持 video.src videoUrl } else if (Hls.isSupported()) { const hls new Hls() hls.loadSource(videoUrl) hls.attachMedia(video) hls.on(Hls.Events.ERROR, (event, data) { console.error(HLS 播放出错, data) }) } })视频流地址在这里用example.com占位实际项目里请替换成你们的流媒体服务地址。几个实战细节如果页面有多个m3u8播放器实例记得在onUnmounted里调用hls.destroy()释放资源autoplay属性在带声音的情况下会被浏览器拦截所以自动播放通常要配合muted属性直播场景要监听Hls.Events.BUFFER_APPENDING之类的生命周期才能正确处理视频卡顿后的重连逻辑。项目里我还封装过一个hls播放器组件props接收url、autoplay、mutedemit事件抛出错误和播放状态这样所有页面复用同一套播放逻辑排查问题也只要改一个文件。5.2 腾讯地图与Mapbox在Vue里的接入要点vue里接入腾讯地图、Mapbox也是热门需求。先说腾讯地图GL JS它目前没有一个官方维护到位的npm包我实际项目里还是用script标签在public/index.html引入它的JS文件然后在需要的组件onMounted里初始化。script srchttps://map.qq.com/api/gljs?v1.expkey你的密钥/scriptimport { onMounted, ref } from vue const mapContainer ref(null) onMounted(() { const center new TMap.LatLng(39.984120, 116.307484) const map new TMap.Map(mapContainer.value, { center, zoom: 15 }) // 记得存map引用组件卸载时销毁 })Mapbox GL的接入方式更规范一点有npm包npm install mapbox-glimport mapboxgl from mapbox-gl // 记得设置 token mapboxgl.accessToken 你的token const map new mapboxgl.Map({ container: mapContainer.value, style: mapbox://styles/mapbox/streets-v12, center: [116.307484, 39.984120], zoom: 11 })接入地图最容易踩的坑有三个一是容器必须有确定的高度地图初始化的容器高度为0会导致空白页面我排查过好几个“地图不显示”的问题最后都是父容器height没撑开二是token写死在前端代码里有泄露风险建议从环境变量读取并在服务端做域名白名单限制三是地图实例要用模块级变量或window级变量保存在onUnmounted里调用remove()销毁不然页面切换后内存占用会持续上涨。5.3 HTML表格与WPS、MD格式互转的实用技巧先说WPS表格WPS和Excel都支持HTML表格文件所以你不用生成复杂的xlsx二进制直接把table标签包裹成HTML用Blob下载成.xls后缀就行。function exportTableToWps() { const table document.getElementById(dataTable).outerHTML const html html xmlns:xurn:schemas-microsoft-com:office:excel headmeta charsetutf-8/head bodytable${table}/table/body /html const blob new Blob([\ufeff html], { type: application/vnd.ms-excel }) const link document.createElement(a) link.href URL.createObjectURL(blob) link.download 报表.xls link.click() URL.revokeObjectURL(link.href) }注意Blob前面拼了\ufeff这个BOM不然用Excel/WPS打开HTML表格时中文可能乱码。用这个方案导出的文件本质上还是HTML数据量大的报表建议直接用SheetJSxlsx库生成真正的xlsx。至于HTML转Markdown前端用turndown库最省事npm install turndownimport TurndownService from turndown const turndownService new TurndownService() const markdown turndownService.turndown(h1标题/h1p正文内容/p) console.log(markdown) // 输出# 标题 // 正文内容这个能力常用于富文本编辑器到Markdown的转换内部文档系统、博客后台都能用。我在项目里把这段逻辑封装成了util函数富文本内容在进入数据库前统一转成Markdown前端展示就再也不用处理乱糟糟的HTML片段了。6. 工程化收尾打包进SpringBoot与团队协作单个页面写得再好交付环节出问题一样前功尽弃。最后这部分讲打包、协作和组件沉淀对应热搜里的vue打包放进springboot、vue项目源码怎么发给别人、vue低代码平台这几个关键词都是工程落地时绕不开的事。6.1 Vue项目打包进SpringBoot的完整配置前后端分离项目最常见的交付场景就是Vue构建产物放进Spring Boot里作为一个整体部署。基本原理不复杂npm run build生成dist静态目录后端把dist里的文件作为静态资源托管再把前端路由的history模式请求转发到index.html就行。最简单的做法是把dist目录内容复制到Spring Boot的src/main/resources/static下面。但要注意两点第一如果后端接口和前端页面在同一域名下Vite的base配置要么保持默认根路径要么配合后端context-path设置成对应前缀第二前端用了history模式路由后端必须配置SPA回退否则直接访问/xxx这类路径会返回404。一个常见的配置片段长这样Configuration public class SpaWebConfig implements WebMvcConfigurer { Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController(/{path:[^\\.]*}) .setViewName(forward:/index.html); } }更工程化的做法是用frontend-maven-plugin把前端构建集成到Maven生命周期里前端代码和后端代码一个命令打包spring-boot打包时自动执行npm install和npm run build把产物拷进static避免手工拷贝的版本错乱问题。我的经验是不管用哪种方式dist和target都不应该提交到Git打包产物在部署环境重新生成才是可控的交付链路。6.2 源码协作与依赖锁定上一节提到发源码这里再补充团队协作场景。团队成员之间最稳的协作方式是用Git仓库加package-lock.json。lock文件的价值在于锁版本同一个package.json在不同时间npm install解析出来的依赖版本可能不同lock文件彻底杜绝了这个问题。如果没有lock文件团队里两个人装出来的node_modules可能都不一样线上环境再复现一次行为就不可控了。还要管好镜像源。团队可以统一在项目根目录放.npmrc指定registry指向内部镜像或npmmirror这比每个成员各自改全局配置靠谱。我观察过很多团队依赖不一致、构建失败的源头一半是镜像源不统一一半是Node版本不一致。Node版本管理用nvm或volta都行项目README里写明Node版本是底线要求。如果你接手的一个老项目在别人电脑上正常、在你电脑上报错先拿node -v对比版本这一招能省掉很多无谓排查。6.3 通用组件沉淀与轻量低代码思路最后聊聊组件沉淀这是前端项目规模化之后必须面对的问题。一个团队如果每个项目都重写表格、表单、日历那效率一定上不去。fullcalendar就是这类可复用组件的典型Vue 3接入只需要安装 fullcalendar/vue3、daygrid、timegrid 几个包封装一层后所有排班、日程页面都可以复用。npm install fullcalendar/vue3 fullcalendar/daygrid fullcalendar/timegrid低代码平台的热搜也与此相关。真正从零做一个低代码平台成本很高但轻量方案完全可以落到自己项目里维护一份JSON Schema描述表单的字段、类型、校验规则和布局然后用递归组件动态渲染出表单页面。Vue的component is、插槽、渲染函数在这套方案里是主力本质还是“用JavaScript定义数据结构用Vue把结构翻译成可交互的HTML”。数据驱动界面这件事做起来比听起来简单值得在合适的项目里试点。我第一版只做了表单渲染两个月后业务方自己都开始维护配置JSON了效果比预期好很多。最后分享一个习惯我每年都会把vue面试题翻一遍目的不是跳槽而是用它检验基础有没有松动。环境配置、路由参数、插槽、类型判断、数值处理、错误排查这些点看着细碎但它们拼在一起才是一个能扛住需求变化的Vue项目。踩过的坑不会消失记录下来、整理成checklist下次接手项目时你会感谢当时的自己。
返回列表