多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

Vue项目实战全链路:环境配置、路由插槽到SpringBoot部署

Vue项目实战全链路:环境配置、路由插槽到SpringBoot部署 1. 从“Vue 1.26”说起这不是一个官方版本号很多人看到“Vue 1.26”第一反应是“Vue什么时候出的1.x新版本”其实不是。这是我给自己持续维护的一套Vue实战笔记起的迭代代号——1代表主方向是第一线开发落地经验26代表已经沉淀到第26期内容。之所以用它当标题是因为我翻了一下近半年来收集的Vue相关热搜词发现大家搜的东西高度集中环境配置怎么搞、路由参数怎么传、插槽到底怎么用、项目打包怎么放进SpringBoot、面试题怎么答这些几乎全是同一个东西——Vue项目从零到上线的完整链路。这篇文章的价值就一个把“Vue项目开发”这件事拆成一条可执行的路线每一段都给出我在实际项目中验证过的方案。适合三类人刚学完教程但不知道怎么做真实项目的新手被公司项目催着上手的准初级工程师以及准备面试前想系统梳理一遍Vue要点的求职者。我这里说的“Vue 1.26”本质上不是某个具体版本而是这套实战内容体系的代号下面所有内容都围绕一个真实项目展开基于SpringBoot加Vue的商品管理系统。2. 环境配置与项目初始化最容易被低估的一关热搜词里“vue安装及环境配置”“vscode保姆级安装vue”“vue安装依赖”出现频率极高。我不止一次看到有人卡在环境上两三天进不了编码状态所以这一节把初始化流程和坑都铺开讲。2.1 基础环境的版本选择做Vue项目第一步是把Node.js装好。这里先给一个关键结论不要装太老的Node也不要追最新版。目前实测下来Node.js 18.20.x和20.x系列是最稳的Vite官方要求Node版本18以上而旧版本会直接报错“Vite 5 requires Node.js 18”。工具或软件推荐版本说明Node.js18.20.x / 20.x LTS长期支持版兼容Vite和Webpack5npm10.x随Node自带也可以换成pnpm速度更快Vite5.x新一代构建工具冷启动比Vue CLI快很多Vue3.4当前主力版本组合式API成熟稳定Vue Router4.x配合Vue3使用语法和2.x有差异Pinia2.x官方的Vue3状态管理方案替代Vuex为什么我推荐Vite而不是Vue CLIVue CLI基于Webpack虽然生态成熟但冷启动开发服务器需要5到15秒热更新也经常有延迟。Vite利用原生ESModule冷启动基本在1秒以内改动代码后刷新几乎是瞬时的。做商品管理系统这种中型项目开发体验差距非常大。另外提一句Node安装后记得在终端检查版本输入node -v npm -v如果npm下载依赖很慢可以配置镜像。这里我建议配置npmmirror仓库速度稳定而且很多大厂内部部署也用同类的代理仓库逻辑提前习惯没坏处。npm config set registry https://registry.npmmirror.com2.2 用Vite创建一个Vue3项目并安装核心依赖我在实战中创建项目一般直接跑create-vue它是Vue官方的脚手架会帮你把Vue Router、Pinia、ESLint等选项一次问清楚。npm create vuelatest脚手架会问几个问题我通常这样选项目名称填shop-adminTypeScript如果团队熟悉TS就选Yes不熟就先选No别硬上是否安装Vue RouterYes是否安装PiniaYes是否安装ESLintYes是否安装Prettier建议Yes统一代码格式创建完进入目录安装依赖并启动cd shop-admin npm install npm run dev打开终端提示的本地地址如果能看到Vue欢迎页说明基础环境已经通了。这里有一个常见的坑npm install时出现“ERESOLVE unable to resolve dependency tree”。这个错误通常是依赖版本冲突引起的我一般先试试npm install --legacy-peer-deps为什么会有这个冲突因为很多第三方库还没跟上最新Vue版本peerDependencies里写的还是旧版本范围npm 7以后的严格解析模式会拒绝这种隐患。有时候直接改registry或者升级node版本反而更麻烦用legacy-peer-deps是快速绕开问题的手段。2.3 目录结构设计从一开始就留好扩展位项目创建好之后我会第一时间把src目录重新组织一遍。很多人小项目随便写等需求一多代码全堆在文件里改起来头疼。我习惯的结构是这样src/ ├─ api/ # 接口请求封装 │ ├─ modules/ # 按业务模块拆分请求 │ └─ request.js # axios实例 ├─ assets/ # 静态资源图片、全站样式 ├─ components/ # 通用组件按需建子文件夹 ├─ router/ # 路由配置 │ ├─ index.js │ └─ routes.js ├─ stores/ # Pinia状态管理 ├─ views/ # 页面级组件 │ ├─ product/ # 商品模块页面 │ ├─ order/ # 订单模块页面 │ └─ dashboard/ # 统计页面 ├─ utils/ # 工具函数 ├─ App.vue └─ main.js为什么要这样分核心原因是让模块边界清晰。商品管理系统虽然不复杂但商品、订单、统计、登录这些模块天然是独立的你后面加需求、换接口、做权限控制都会省很多事。比如动态路由如果路由配置散落在某个业务文件里而没有一个独立的routes.js权限控制就无法复用。3. 路由与组件复用Vue应用的两条主线热搜词里“vue路由”“vue动态路由”“vue路由参数”“slot vue”“vue插槽”都是高频搜索。我把它们放在一起讲是因为商品管理系统的骨架本质上就是“路由控制页面切换组件控制页面内容”这两件事搞明白Vue就算是真正入门了。3.1 动态路由与路由参数不只是“传个值”的事动态路由最常见的场景是菜单由后端返回不同角色看到不同的菜单点击菜单之后动态注册对应的页面路由。先看基础写法。路由参数分两种路径参数和查询参数。// 路径参数 { path: /product/detail/:id, name: ProductDetail, component: () import(/views/product/ProductDetail.vue) }在页面里读取路径参数import { useRoute, useRouter } from vue-router const route useRoute() const router useRouter() // 读取路径参数: /product/detail/1001 console.log(route.params.id) // 1001 // 查询参数: /product/list?keyword手机 console.log(route.query.keyword) // 手机 // 跳转方式 const goDetail (id) { router.push({ name: ProductDetail, params: { id } }) }这里要特别提醒一个我踩过的坑使用路径参数时组件会被复用。比如你在列表页点商品A进入详情页再返回列表点商品BVue会认为还是同一个路由不会重新走生命周期。此时你不能只在onMounted里请求数据要用watch监听route的变化import { watch } from vue import { useRoute } from vue-router const route useRoute() watch( () route.params.id, (newId) { if (newId) { getProductDetail(newId) } }, { immediate: true } )动态路由的“动态”体现在两个层面一是上面这种带参数的路由路径二是运行时通过addRoute添加路由。权限菜单就是靠addRoute实现的。const addDynamicRoutes (menus) { menus.forEach((menu) { const route { path: /${menu.path}, name: menu.name, component: () import(/views/${menu.component}.vue) } router.addRoute(Layout, route) }) } // 下一次路径切换时新路由才会生效 router.replace(/dashboard)实践中的注意点addRoute之后必须调用router.replace或一次新的导航新路由才注册到当前导航流程里。服务端返回的菜单component字段前端最好做一个目录映射表不要直接拼接动态import字符串否则打包后组件路径可能对不上。刷新页面时动态路由会丢失需要在全局守卫里做“先登录后取菜单再放行”的逻辑否则一刷新就白屏。这个逻辑通常放在router.beforeEach里。3.2 插槽从组件复用角度理解slot“vue插槽”是热搜里的高频词很多人觉得插槽难是因为把它当成一个语法点来背。我建议把插槽理解成一个“组件里的占位区域”。商品管理系统的卡片组件可以说明插槽的价值。假设你要做一个统一的统计卡片左边放数字右边放操作按钮不同页面按钮不一样。如果没有插槽你得写死按钮要么就复制好几个卡片组件。用插槽以后这个卡片组件就是通用的。!-- StatCard.vue -- template div classstat-card div classstat-card__info p classstat-card__title{{ title }}/p p classstat-card__value{{ value }}/p /div div classstat-card__action !-- 默认插槽: 调用者传入任意内容 -- slot nameaction/slot /div /div /template script setup defineProps({ title: String, value: [Number, String] }) /script调用方式StatCard title商品总数 :valuetotal template #action el-button typeprimary clickopenAdd新增商品/el-button /template /StatCard StatCard title今日订单 :valuetodayOrders template #action el-button clickgoOrderList查看详情/el-button /template /StatCard再看作用域插槽。它的作用是把子组件的数据暴露给父组件让父组件决定怎么渲染子组件决定数据从哪来。商品列表组件里状态是子组件自己请求的但具体怎么显示让父组件决定。!-- ProductList.vue -- template div classproduct-list slot nameitem v-forproduct in products :productproduct :keyproduct.id/slot /div /template script setup const products ref([ { id: 1, name: 商品A, price: 99, status: 1 }, { id: 2, name: 商品B, price: 199, status: 0 } ]) /script父组件使用ProductList template #item{ product } div classcustom-card span{{ product.name }}/span span{{ product.status 1 ? 在售 : 下架 }}/span /div /template /ProductList插槽用多了以后你会发现组件的复用能力一下子提升一大截项目里不再需要堆一摞互相复制粘贴的组件。3.3 折叠展开与多文件上传两个高频实战场景的写法热词里“vue 内容折叠展开”“vue 多文件上传”都是真实业务里常见的功能我直接给出可复用的正交思路。折叠展开无非就是控制内容的显隐状态。我自己最推荐用v-model的思想做通用折叠组件!-- CollapseSection.vue -- template div classcollapse-section div classcollapse-section__header clicktoggle span{{ title }}/span svg :class{ is-open: modelValue } classarrow-icon.../svg /div transition namecollapse div v-showmodelValue classcollapse-section__content slot/slot /div /transition /div /template script setup const props defineProps({ title: String, modelValue: { type: Boolean, default: true } }) const emit defineEmits([update:modelValue]) const toggle () emit(update:modelValue, !props.modelValue) /script使用CollapseSection v-modelopenFilters title高级筛选 div价格区间、库存范围等筛选内容/div /CollapseSection这里用v-show而不是v-if的好处是内容切换时不丢失表单里已填写的临时状态。高级筛选场景中用户勾选了一半条件不小心折叠了一下再展开时内容还在体验好很多。如果直接用v-if状态会被重置。多文件上传在商品管理里主要是商品图片。我用的是el-upload加FormData的方式关键点是处理好文件和提交逻辑的分离。el-upload :show-file-listfalse :before-uploadhandleBeforeUpload acceptimage/png,image/jpeg,image/webp v-model:file-listfileList el-button typeprimary点击上传/el-button /el-upload script setup const fileList ref([]) const uploadFile (file) { const formData new FormData() formData.append(file, file) // 使用你封装好的request实例上传 return uploadApi(formData) } const handleBeforeUpload (file) { const isImage file.type.startsWith(image/) if (!isImage) { console.warn(只能上传图片) return false } if (file.size / 1024 / 1024 2) { console.warn(图片不能超过2MB) return false } return true } /script一个优先级很高的细节给上传接口加上中断时限和进度提示。多张图片一起传时如果同时请求9张浏览器并发数一满其他接口全部排队页面会卡成负数体验。成熟做法是限制并发为3到4个用Promise控制队列。4. 商品管理系统实战从接口约定到打包部署热词里“基于springboot vue商品管理系统的设计与实现”“vue打包放进springboot中”“基于springboot vue的项目”出现得很多。这类综合性项目最大的教学价值不是炫技而是把前端的模块、组件、路由、状态、打包流程完整串起来。我挑核心环节拆开讲。4.1 前端与后端接口约定先定规范再写代码做前后端分离项目最容易翻车的是接口约定不一致。一个商品列表接口后端返回{code: 0, data: [...], msg: success}前端却按{status: 200, result: [...]}去解析必炸。我的做法是先和后端约定统一响应体然后在前端统一封装axios实例所有请求都走同一个拦截器。// src/api/request.js import axios from axios const request axios.create({ baseURL: /api, timeout: 10000 }) // 请求拦截器 request.interceptors.request.use( (config) { const token localStorage.getItem(token) if (token) { config.headers.Authorization Bearer ${token} } return config }, (error) Promise.reject(error) ) // 响应拦截器 request.interceptors.response.use( (response) { const res response.data if (res.code ! 200) { // 统一错误处理 console.warn(res.msg) return Promise.reject(new Error(res.msg)) } return res.data }, (error) { if (error.response error.response.status 401) { // 登录过期跳登录页 router.push(/login) } return Promise.reject(error) } ) export default request注意统一响应体的格式约定成文档贴到团队的接口文档里{ code: 200, // 业务状态码200为成功 data: {}, // 业务数据 msg: success // 提示信息 }商品模块的接口封装单独放一个文件// src/api/modules/product.js import request from /api/request export function getProductList(params) { return request.get(/product/list, { params }) } export function createProduct(data) { return request.post(/product/save, data) } export function updateProduct(data) { return request.put(/product/update, data) } export function deleteProduct(id) { return request.delete(/product/delete/${id}) }为什么我要这样分一层因为页面里不应该出现一长串axios.get带有完整URL的代码接口一多以后改动全会集中在api/modules目录下排查和替换都很方便。4.2 使用ECharts绘制双柱状图从数据到图表的最短路径热搜词里有“vue中用echarts画两个柱状统计图”这个在商品管理系统里通常是“近7日销售额与订单量对比”。先把ECharts依赖装上npm install echarts然后不要怎么做不要在每个页面里直接引入ECharts然后手写初始化。那是旧写法的做法大型项目里会搞得很快。推荐用组件的方式封装!-- BaseChart.vue -- template div refchartRef classbase-chart/div /template script setup import { onMounted, onBeforeUnmount, ref, watch } from vue import * as echarts from echarts const props defineProps({ option: { type: Object, required: true } }) const chartRef ref(null) let chartInstance null onMounted(() { chartInstance echarts.init(chartRef.value) chartInstance.setOption(props.option) window.addEventListener(resize, handleResize) }) const handleResize () chartInstance chartInstance.resize() watch( () props.option, (newOption) { chartInstance chartInstance.setOption(newOption) }, { deep: true } ) onBeforeUnmount(() { window.removeEventListener(resize, handleResize) chartInstance chartInstance.dispose() }) /script然后写图表配置。双柱状图的本质就是把两个系列都设为bar类型各自的yAxisIndex可以不同const option { tooltip: { trigger: axis }, legend: { data: [销售额, 订单量], top: 0 }, xAxis: { type: category, data: [周一, 周二, 周三, 周四, 周五, 周六, 周日] }, yAxis: [ { type: value, name: 销售额(元) }, { type: value, name: 订单量(单) } ], series: [ { name: 销售额, type: bar, data: [1200, 2300, 1800, 3200, 2800, 4100, 3600], itemStyle: { color: #409EFF }, barWidth: 20 }, { name: 订单量, type: bar, yAxisIndex: 1, data: [12, 25, 18, 32, 28, 41, 36], itemStyle: { color: #67C23A }, barWidth: 20 } ] }关键点在于同一个xAxis下定义两个yAxis订单量因为数值级完全不在一个水平线上所以要单独挂一条Y轴。还有一个实际经验图表容器的父元素必须有明确的高度比如300px否则ECharts初始化的高度会是0整个图直接不显示。这个坑极其常见通常的表现是控制台没报错页面空白一块。4.3 Mock数据让前端不依赖后端也能完整跑通增删改查学习Vue的时候大家常遇到的第一个障碍是“没有后端接口怎么写业务代码”。所以我建议在本地开发阶段使用Mock机制。“vue mock版本 增加修改删除”“mock是怎么体现的”这些热搜词说明很多人对这个机制好奇。Mock的核心思路是拦截请求模拟后端的增删改查逻辑返回真实格式的数据。在Vite下我用的方案是vite-plugin-mock。它会在开发服务器里注册中间件拦截FetchXHR请求。npm install vite-plugin-mock mockjs --save-dev在vite.config.js中配置import { viteMockServe } from vite-plugin-mock export default defineConfig({ plugins: [ viteMockServe({ mockPath: mock, enable: true }) ] })mock目录下写这样一个文件// mock/product.js export default [ { url: /api/product/list, method: get, response: ({ query }) { const list [ { id: 1, name: 手机, price: 2999, stock: 100 }, { id: 2, name: 耳机, price: 499, stock: 200 }, { id: 3, name: 平板, price: 4999, stock: 50 } ] return { code: 200, data: list, msg: success } } }, { url: /api/product/save, method: post, response: ({ body }) { // 新增、修改都走这里 return { code: 200, data: body.id ? 修改成功 : 添加成功, msg: success } } }, { url: /api/product/delete/:id, method: delete, response: () ({ code: 200, data: null, msg: 删除成功 }) } ]Mock的数据默认存在内存里刷新页面会重置。如果想让后端增删改查的结果在页面刷新后仍然保留就得用localStorage维护一份数据或者在后端把Mock改成JSON文件持久化。作为一个演示项目内存版够用了。Mock怎么体现呢核心在于接口路径和真实后端完全一致这样前端写的是“真实”的请求逻辑联调时只需把mock关闭接口自然切到真实后端页面代码不用改一行。这也是我推荐这种方案而不是自己在axios层写死假数据的原因。在商品管理系统里操作流程是页面初始化调用getProductList请求商品列表Mock拦截返回数据UI渲染表格。点击“新增”按钮弹出表单提交时调用createProductMock拦截后返回成功。一整套增删改查流程你和后端没有任何接口依赖也可以完整调试。4.4 打包部署把Vue项目放进SpringBoot“vue打包放进springboot中”是热搜词里非常具体的一个场景。很多人卡在这一步是因为前后端分离项目到了部署阶段变成了“如何在SpringBoot里托管前端静态资源”的问题。原理上很简单Vue项目打包后生成dist目录里面是纯静态的HTML/CSS/JS文件。你用Nginx、Tomcat或者SpringBoot的静态资源映射都能把这些文件托管出去。放进SpringBoot里最省事的做法是用SpringBoot把dist目录当作静态资源目录来用。前端操作npm run build这会生成dist目录里面包含index.html和assets等资源文件。需要注意Vite配置里的base路径。默认是相对路径还是绝对路径生产环境经常有差异。我通常会在vite.config.js中设置export default defineConfig({ base: ./, // 让打包后的资源路径变成相对路径可直接放进SpringBoot访问 // ... })如果不设置base默认是/。假如你的SpringBoot接口访问路径是http://localhost:8080/api前端静态页面也在这个端口下那用/没问题。但如果你的项目发布到某个子路径比如http://host/app/那必须设置base为./。后端操作。在SpringBoot的static目录下放前端文件或者用代码指定静态资源映射目录。更贴近实际生产的是把dist目录直接作为static目录。如果你用Maven构建可以在src/main/resources/static下生成前端静态文件# 在后端项目的static目录下把前端dist内容复制进去即可 cp -r dist/* ../demo-backend/src/main/resources/static/如果不想手动复制可以在前端根目录的pom.xml里配置frontend-maven-plugin让后端构建时自动执行npm install和npm run build并把dist目录复制到static下。不过这个插件本身配置有点多小项目手动复制反而更可控。还有一个坑SpringBoot默认把/api路径交给Controller处理如果页面里调用的是/api/product/list接口没匹配上就会404静态资源则被默认的静态资源映射兜住。最后的关键点有两个前端请求的baseURL如果写成/api那么SpringBoot的Controller也需要有RequestMapping(/api)前缀。如果启用了Spring Security要放行静态资源和公开接口的匿名访问否则页面直接弹401。部署完成后访问http://localhost:8080/就能看到前端页面登录权限校验通过后商品管理、订单统计、ECharts图表全部正常工作。5. 高频问题排查与面试要点速查最后这部分写给两个群体正准备Vue面试的人以及被项目里某些灵异问题折磨的开发同学。整理几个最常被问到、也是最常踩坑的点位。5.1 问题排查实录VSCode跳转、div超出隐藏、PDF显示等VSCode中点击标签无法跳转。这基本是Volar插件老版本在Vue3下的老问题排查思路很简单确保安装的是“Vue Language Features (Volar)”并禁用早年的Vetur插件。如果还是跳不动检查Vue Language Features版本新版在用takeover模式时可以不依赖TS特性也能正常跳转。重启VSCode后再试一次。div超出隐藏显示省略号。这个场景是文本过长时的处理。记住三个CSS属性必须同时写.ellipsis { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }对多行文本就需要额外加.ellipsis-multi { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden; }但实际使用中line-clamp只对块级容器有效父级最好别乱用flex布局否则浏览器对这个属性和flex嵌套的组合解析容易失效。我通常的做法是在商品名称单元格上直接套一个class而不是在全局样式里统一加。Vue中如何显示PDF文件。社区的常见方案是iframe加src直接浏览器预览。但这个方案受到浏览器PDF插件影响。如果需要体验稳定推荐用pdfjs-dist或者vue-pdf-embed组件。pdfjs-dist的兼容性更好但要注意它目前是ESM模块在构建时会有chunks体积警告。如果你只是演示项目直接iframe最省事内容安全风险还可以靠后端设置Content-Disposition头来控制。VUE中如何播放m3u8视频。“vue播放m3u8免安装”这个热搜词我推测是想找一个不需要额外安装桌面播放器、直接在网页里播放直播流或切片视频的方案。m3u8本质是HLS协议下的播放列表主流浏览器原生不支持需要借助hls.js或video.js加hls插件。在Vue里最推荐hls.js包体积小集成简单npm install hls.jstemplate video refvideoRef controls autoplay muted/video /template script setup import { ref, onMounted } from vue import Hls from hls.js const videoRef ref(null) onMounted(() { const video videoRef.value const videoUrl https://example.com/live/index.m3u8 if (Hls.isSupported()) { const hls new Hls() hls.loadSource(videoUrl) hls.attachMedia(video) } else if (video.canPlayType(application/vnd.apple.mpegurl)) { // Safari等原生支持HLS的浏览器 video.src videoUrl } }) /script这里有两个细节值得注意一是在播放直播流时autoplay muted属性一定要带上否则浏览器自动播放策略会拦截声音播放。二是dev环境下如果遇到跨域问题m3u8和ts切片地址的CORS头必须正确配置后端回调要带上Access-Control-Allow-Origin。Canvas在Vue2和Vue3中的区别。其实Canvas本身没有Vue版本的区别区别在于你初始化以及销毁Canvas的时机。Vue2常用mounted加实例属性保存上下文Vue3用onMounted加ref引用然后在onBeforeUnmount里做清理。用Canvas做商品二维码或图片验证码时注意高分屏的清晰度需要手动处理devicePixelRatioconst canvas canvasRef.value const ctx canvas.getContext(2d) const dpr window.devicePixelRatio || 1 const { width, height } canvas.getBoundingClientRect() canvas.width width * dpr canvas.height height * dpr ctx.scale(dpr, dpr)如果不做这层处理在Retina屏幕上皮皮虾清晰度会明显发糊。5.2 Vue面试题背题之外的思考热搜词里“vue面试题”“vue前端面试题”出现频率很高。面试题不是背答案是要真理解。我按经验挑了容易出理解偏差的几个。computed和watch怎么选。我的判断标准很简单能算出来的就computed有副作用的就watch。computed必须是一个返回值的表达式watch则适合处理“变化之后要去请求接口、去更新另一个状态”这种异步逻辑。例如商品列表的搜索关键词你用一个computed去过滤本地列表是合理的但关键词变化后要重新请求后端那就必须在watch里处理。v-model的本质是什么。Vue3的v-model是modelValue属性和update:modelValue事件的语法糖input :modelValuesearchText update:modelValuesearchText $event /所以自定义组件要支持v-model代码里得这样写script setup const props defineProps({ modelValue: String }) const emit defineEmits([update:modelValue]) /scriptkey的作用到底是干嘛的。在渲染列表时key通过它来确定节点是否需要复用。只要key相同且值不变Vue就会复用这个DOM节点不销毁重建。所以删除列表项时如果用index当key交叉复用带来的状态错乱几乎无法避免。正确做法是用商品id或唯一键。Vue2和Vue3的核心差异。面试里我最常答的几点响应式原理从Object.defineProperty变成了Proxy可以检测到对象属性增删和数组下标变化全局API变成了应用实例API组件通信和逻辑复用用组合式APIFragment、Teleport等新特性。Nuxt和Vue的区别。简单说Nuxt是建立在Vue之上的一套框架。Vue是前端库负责视图层和组件Nuxt则是集成了Vue、路由、SSR/SSG、自动导入、服务端API等功能的全栈框架。用Nuxt不是你就不写Vue了而是你写Vue时多了很多开箱即用的能力。做SEO要求高的站点或者需要在服务端渲染数据的项目优先Nuxt中后台管理系统普通Vue加路由就够了Nuxt的SSR优势在这里没有发挥空间。Vue中如何监听页面宽度变化并做响应式布局。设备宽度变化适配是后台项目常见的需求可以封装一个useWindowSize组合式函数import { ref, onMounted, onBeforeUnmount } from vue const width ref(window.innerWidth) const height ref(window.innerHeight) function handleResize() { width.value window.innerWidth height.value window.innerHeight } onMounted(() window.addEventListener(resize, handleResize)) onBeforeUnmount(() window.removeEventListener(resize, handleResize)) return { width, height }这个方法只需要在商品管理系统的统计页里监听width左边菜单宽或窄柱状图resize各区域显隐切换逻辑清晰且不会污染全局。5.3 值得一提的工具链细节Mock、VS Code插件、热词里的Mapbox和腾讯地图热搜词里“mapbox vue”“用在vue里的腾讯地图”“vue lynx”“copilotkit vue”这几个词单独说一下。前端地图和AI组件集成越来越普遍但不管用的是Mapbox还是腾讯地图思路都是一样的先在Vue的生命周期里加载地图SDK再把地图实例挂载到ref的容器上最后在组件销毁时清理实例。腾讯地图在Vue里的接入方式通常是这样import { onMounted, onBeforeUnmount } from vue let mapInstance null onMounted(() { const TMap window.TMap mapInstance new TMap.Map(mapContainer, { center: new TMap.LatLng(39.984104, 116.307503), zoom: 12 }) }) onBeforeUnmount(() { mapInstance mapInstance.destroy() })这个写法同样适用于Mapbox。其实没必要特意给每个地图都封装一个巨复杂的组件而是把地图相关操作集中到一个地图模块里页面只用restful接口把经纬度和标记点传进去反而比外部引入大改架构更稳定。“copilotkit vue”这类AI相关的集成本质上是把LLM的流式输出接入前端。配合热搜词“vue python sse”这里就变成了一个有趣的话题前端通过EventSource或者fetch流式读取后端推理进度然后把大模型输出渲染成用户界面上的一段段文本。技术点在于SSE的解析逻辑以及把markdown渲染到页面时注意格式化转义。我会在商品管理系统里加一个通用的AI工单助手组件核心代码不复杂但实现了以后项目演示的震撼力截然不同。6. “Vue 1.26”之外我的一点实际体会写到这这篇笔记核心内容基本铺完了。最后分享几个我在这26期Vue实战里反复验证过的体会。一个是**“渐进式”这三个字说出来容易做起来难**。很多人一开始就想把项目做成完美架构结果大量时间花在“思考怎么用Pinia”而不是“先让页面跑起来”。我自己的节奏是第一版专门做烂先把商品增删改查跑通在这个过程中把所有逻辑都搞清楚第二版再重构成组件化和状态管理方案。重写是收益很明确的投资因为只有你亲手踩过坑才知道哪些抽象是必须的。另一个是项目上线的成功往往取决于接口约定和Mock的严格程度。商品管理系统是一个蛮典型的“后端未就绪前端先行”项目没有Mock前端联调效率低得惊人。真实项目里想尽办法让前端在依赖后端前先把页面流动起来是实战效率提升的关键。最后一个是面试官真正想听的通常不是结论而是过程。被问到“computed和watch怎么选”你光说“能算出来的用computed”是不够的你得讲一个具体场景你在商品列表里根据搜索关键词、价格区间和库存状态计算筛选结果三个条件一起变computed只会在依赖变化时重新计算所以性能是安全的。这样一说面试官立刻知道你是在项目中真正用过而不是背八股。“Vue 1.26”这期内容就到这里这些方案是我在多套商品管理系统、数据看板项目和地图应用中验证过的你完全可以拿去当模板也应该不断往里面填自己的坑和新写好的代码片段。下一篇我打算写Canvas可视化在Vue里的实际应用到时候把商品数据可视化从柱状图换到更硬核的场景到时接着聊。
返回列表