#记录在2025.12-2026.3,从一个Vue新手到能独立开发项目的成长历程,包含大量实战中遇到的问题和解决方案。第一段实习总结。
#时间:2026.3.3
目录
一.项目环境与工具
二.Git版本控制实战
三.Vue3核心知识
四、路由与状态管理
五、CSS与布局技巧
六、性能优化
七、移动端开发
八、调试与部署
九、开发工具与插件
十、常见问题与解决方案
一.项目环境与工具
1.1 开发模式与生产模式的区别
开发流程:pnpm run dev→ vite dev server → 内存编译 → 浏览器
生产流程:pnpm build→ 生成dist目录 → pnpm start→ vite预览服务器 → 提供dist文件
为什么 pnpm start会报错缺少dist目录?
pnpm run dev:使用vite启动开发服务器,直接在内存中编译,不需要dist目录pnpm start:使用vite预览已构建的生产版本,必须要有dist目录- 解决步骤:
1# 1. 先构建项目 2pnpm build 3# 2. 再预览 4pnpm start
1.2 Package.json 文件解析
1{ 2 "private": true, // 防止误发布到npm 3 "scripts": { 4 "dev": "vite", // 开发模式 5 "build": "vite build", // 构建生产版本 6 "start": "vite preview" // 预览生产版本 7 } 8}
重要文件:
package.json:主配置文件,必须提交到gitpackage.bak.json:自动生成的备份文件,不要提交到git
1.3 Node模块管理演进
幽灵依赖问题:项目可以直接引用非直接依赖的包,可能导致:
- 间接依赖升级破坏代码
- 迁移到pnpm时报错
解决方案对比:
1# npm v2-v3: 嵌套结构(大量重复安装) 2# npm v5+ / yarn: 扁平化结构(幽灵依赖问题) 3# pnpm: 符号链接 + 硬链接(最佳实践)
pnpm的解决方案:
1node_modules/ 2├── .pnpm/ # 所有包的实际存储位置 3│ ├── express@4.18.0/ 4│ ├── debug@3.0.0/ 5│ └── debug@4.0.0/ 6├── express/ → .pnpm/express@4.18.0/ # 符号链接 7└── debug/ → .pnpm/debug@3.0.0/ # 符号链接
1.4 命令行工具
npx的作用:
1# 1. 避免全局安装 2npx create-vite@latest my-project 3 4# 2. 运行项目依赖的二进制文件 5npx eslint --fix . 6 7# 3. 使用指定版本的包 8npx webpack@4.0.0 --version
Oh My Zsh配置:
1# 安装 2sh -c "$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" 3 4# 常用别名配置(添加到 ~/.zshrc) 5alias gs='git status' 6alias ga='git add' 7alias gd='git diff' 8alias gb='git branch -v' 9alias gcm='git commit -m' 10alias gam='git commit --amend' 11alias gck='git checkout' 12alias gcp='git cherry-pick' 13alias gp='git push' 14alias gpf='git push -f' 15alias gplm='git pull origin master' 16alias gpo='git pull origin' 17alias start='pnpm start' 18alias dev='pnpm run dev' 19alias pi='pnpm install' 20alias pj="cd ~/project" 21alias std="cd ~/study"
二.Git版本控制实战
2.1 基础配置
1# 配置用户信息 2git config --global user.name "Your Name" 3git config --global user.email "email@example.com" 4 5# 查看SSH密钥 6cat ~/.ssh/id_rsa.pub 7 8# 生成SSH密钥 9ssh-keygen -t rsa -b 4096
2.2 解决合并冲突
场景一:功能分支合并到QA环境
1# 1. 基于当前分支创建临时分支 2git checkout -b ai-photo-v1.3-qa 3 4# 2. 合并QA分支代码 5git pull origin qa 6 7# 3. 解决冲突 8# 搜索所有冲突标记 9grep -r "<<<<" . 10# 或全局搜索 <<<<< 然后手动解决 11 12# 4. 提交解决后的代码 13git add . 14git commit -m 'fix: resolve conflict with qa' 15 16# 5. 推送到远程 17git push --set-upstream origin ai-photo-v1.3-qa 18 19# 6. 创建Merge Request合并到QA
场景二:已上线代码合并master
1# 1. 切换到要合并的分支 2git checkout ai-photo-v1.3 3 4# 2. 拉取master最新代码 5git pull origin master 6 7# 3. 解决冲突 8# ...(同场景一) 9 10# 4. 提交并推送 11git add . 12git commit -m 'fix: merge master updates' 13git push
2.3 Git Pull相关配置
问题:执行git pull时总是打开编辑器要求输入合并信息
解决方案:
1# 设置pull时自动合并,不打开编辑器 2git config --global pull.rebase false 3git config --global pull.ff only
pull配置选项详解:
1# 1. pull.rebase 2# false: 默认,执行git merge(创建合并提交) 3# true: 执行git rebase(变基,保持线性历史) 4 5# 2. pull.merge 6# 老版本配置,已弃用 7 8# 3. pull.commit 9# 不存在此配置
推荐配置:
1# 保持干净的提交历史 2git config --global pull.rebase true 3git config --global rebase.autoStash true
2.4 GitLab与GitHub对比
| 特性 | GitLab | GitHub |
|---|---|---|
| 部署方式 | 支持自托管 | 仅SaaS |
| CI/CD | 内置GitLab CI | GitHub Actions |
| 项目管理 | 内置完整工具链 | 依赖第三方集成 |
| 社区生态 | 较小 | 庞大 |
| 企业级功能 | 内置更多 | 需付费 |
选择建议:
- 需要完整DevOps工具链、自托管 → GitLab
- 依赖社区生态、开源协作 → GitHub
三.Vue3核心知识
3.1 组件通信
Props传递:
1<!-- 子组件 --> 2<script setup lang="ts"> 3// 方式1:简单声明 4defineProps(['title', 'data']); 5 6// 方式2:带类型 7defineProps<{ 8 title: string; 9 data?: string; 10 viewCount?: number | string; 11}>(); 12 13// 方式3:带默认值 14withDefaults(defineProps<{ 15 title: string; 16 data?: string; 17 viewCount?: number | string; 18}>(), { 19 title: '默认标题', 20 data: '', 21 viewCount: 0 22}); 23</script>
Emit事件:
1<!-- 子组件 --> 2<script setup lang="ts"> 3const emit = defineEmits<{ 4 (e: 'change', value: string): void; 5 (e: 'submit', data: any): void; 6}>(); 7 8const handleClick = () => { 9 emit('change', '新值'); 10 emit('submit', { id: 1, name: '测试' }); 11}; 12</script>
defineExpose暴露组件方法:
1<!-- 子组件 --> 2<script setup lang="ts"> 3const internalMethod = () => { 4 console.log('内部方法'); 5}; 6 7// 暴露给父组件 8defineExpose({ 9 internalMethod 10}); 11</script> 12 13<!-- 父组件 --> 14<template> 15 <ChildComponent ref="childRef" /> 16</template> 17 18<script setup lang="ts"> 19import { ref } from 'vue'; 20 21const childRef = ref(); 22 23const callChildMethod = () => { 24 childRef.value?.internalMethod(); 25}; 26</script>
3.2 响应式数据
推荐结构:
1// 1. import导入 2import { ref, reactive, computed, watch } from 'vue'; 3import { useRoute } from 'vue-router'; 4 5// 2. 自定义hooks 6const route = useRoute(); 7 8// 3. 响应式数据定义 9const count = ref(0); 10const user = reactive({ 11 name: '', 12 age: 0 13}); 14const activeIndex = ref(0); 15 16// 4. 计算属性 17const doubleCount = computed(() => count.value * 2); 18 19// 5. 方法定义 20const increment = () => { 21 count.value++; 22}; 23 24const toggleActive = (index: number) => { 25 activeIndex.value = index; 26}; 27 28// 6. 生命周期和监听 29onMounted(() => { 30 console.log('组件挂载'); 31}); 32 33watch(count, (newVal, oldVal) => { 34 console.log(`count从${oldVal}变为${newVal}`); 35});
3.3 TypeScript技巧
keyof提取键名:
1const person = { name: '小明', age: 25, city: '北京' }; 2type Keys = keyof typeof person; // 'name' | 'age' | 'city' 3 4// 在路由中使用 5export const routerTabNameMap = { 6 Map: 'AI导览', 7 Story: '故事讲解', 8 Photo: 'AI出片', 9 My: '我的', 10}; 11export type IRouterName = keyof typeof routerTabNameMap;
JSDoc注释:
/** xxx */ 的形式。
1/** 用户信息接口 */ 2export interface IUserInfo { 3 /** 用户ID */ 4 id: number; 5 /** 用户姓名 */ 6 name: string; 7 /** 用户年龄 */ 8 age?: number; 9 /** 用户状态 */ 10 status: UserStatusEnum; 11} 12 13/** 用户状态枚举 */ 14export enum UserStatusEnum { 15 /** 正常 */ 16 NORMAL = 1, 17 /** 禁用 */ 18 DISABLED = 2, 19 /** 删除 */ 20 DELETED = 3 21}
四、路由与状态管理
4.1 Vue Router实战
路由配置:
1import type { RouteRecordRaw } from 'vue-router'; 2 3const routes: RouteRecordRaw[] = [ 4 { 5 path: '/', 6 name: 'MainPage', 7 redirect: () => ({ path: '/new/3479' }), // 动态重定向 8 meta: { title: '首页' } 9 }, 10 { 11 path: '/new/:id', 12 name: 'ScenicViewNew', 13 component: () => import('@/views/main/index.vue'), 14 props: true, // 开启props传参 15 meta: { keepAlive: true }, // 路由元信息 16 children: [ 17 { 18 path: '', // 默认子路由 19 redirect: to => ({ name: 'Map', params: to.params }) 20 }, 21 { 22 path: 'map', 23 name: 'Map', 24 component: () => import('@/views/main/map/index.vue') 25 } 26 ] 27 } 28];
常见路由问题解决:
问题:Maximum call stack size exceeded栈溢出错误
原因:路由重定向死循环
1// ❌ 错误示例:嵌套重定向导致死循环 2{ 3 path: '/', 4 redirect: '/new/3479' // 重定向到/new/:id 5}, 6{ 7 path: '/new/:id', 8 redirect: to => ({ name: 'Map', params: to.params }), // 又重定向 9 children: [ 10 { 11 path: 'map', 12 name: 'Map', 13 component: MapComponent, 14 mounted() { 15 // 这里又跳转回首页,形成循环 16 this.$router.push('/'); 17 } 18 } 19 ] 20}
解决方案:
1// 1. 简化重定向链 2{ 3 path: '/', 4 redirect: { name: 'Map', params: { id: '3479' } } // 直接重定向到目标 5} 6 7// 2. 移除中间路由的重定向 8{ 9 path: '/new/:id', 10 component: () => import('@/views/main/index.vue'), 11 children: [ 12 { 13 path: '', // 添加默认子路由 14 redirect: to => ({ name: 'Map', params: to.params }) 15 }, 16 { 17 path: 'map', 18 name: 'Map', 19 component: () => import('@/views/main/map/index.vue') 20 } 21 ] 22} 23 24// 3. 添加路由守卫防止循环 25router.beforeEach((to, from, next) => { 26 if (to.path === from.path) { 27 next(false); // 取消相同路由的导航 28 return; 29 } 30 next(); 31});
router.push vs router.replace:
1// push: 增加路由历史记录 2router.push('/new-page'); 3// 历史记录: [/] -> [/new-page] 4 5// replace: 替换当前路由,不增加历史记录 6router.replace('/new-page'); 7// 历史记录: [/] -> [/new-page] (替换了/)
4.2 Pinia状态管理
Store定义:
1// stores/map.ts 2import { defineStore } from 'pinia'; 3 4export const useMapStore = defineStore('map', { 5 // 状态 6 state: () => ({ 7 scenicAreaInfo: null as ScenicAreaInfo | null, 8 isInAreaOrFalse: false, 9 userPosition: null as Position | null 10 }), 11 12 // 计算属性 13 getters: { 14 hasRelatedScenicArea: (state) => !!state.scenicAreaInfo, 15 getScenicName: (state) => state.scenicAreaInfo?.name || '未知景区' 16 }, 17 18 // 方法 19 actions: { 20 setScenicAreaInfo(data: ScenicAreaInfo) { 21 this.scenicAreaInfo = data; 22 }, 23 24 async fetchScenicInfo(id: string) { 25 const res = await api.getScenicInfo(id); 26 this.setScenicAreaInfo(res.data); 27 } 28 } 29});
组件中使用:
1<script setup lang="ts"> 2import { storeToRefs } from 'pinia'; 3import { useMapStore } from '@/stores/map'; 4 5const mapStore = useMapStore(); 6// 使用storeToRefs保持响应式 7const { hasRelatedScenicArea, scenicAreaInfo } = storeToRefs(mapStore); 8 9// 调用action 10const loadData = async () => { 11 await mapStore.fetchScenicInfo('123'); 12}; 13</script>
五、CSS与布局技巧
5.1 移动端适配方案
安全区域适配:
1/* 适配iPhone刘海屏 */ 2.safe-area { 3 /* 旧版iOS */ 4 padding-top: constant(safe-area-inset-top); 5 padding-top: env(safe-area-inset-top); 6 padding-bottom: constant(safe-area-inset-bottom); 7 padding-bottom: env(safe-area-inset-bottom); 8 padding-left: constant(safe-area-inset-left); 9 padding-left: env(safe-area-inset-left); 10 padding-right: constant(safe-area-inset-right); 11 padding-right: env(safe-area-inset-right); 12} 13 14/* HTML中需要设置 */ 15<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
PostCSS + postcss-pxtorem自动转换:
1// postcss.config.js 2module.exports = { 3 plugins: { 4 'postcss-pxtorem': { 5 rootValue: 37.5, // 设计稿宽度/10,如375px设计稿 -> 37.5 6 propList: ['*'], // 需要转换的属性 7 selectorBlackList: ['ignore-'] // 忽略的类名 8 } 9 } 10};
响应式计算:
1/* 设计稿:750px宽度,元素宽度:375px */ 2.element { 3 /* 375px / 750px * 100vw */ 4 width: 50vw; 5} 6 7/* 更精确的计算 */ 8.element { 9 /* 设计稿尺寸 / 设计稿宽度 * 100vh / 设计稿高度比例 */ 10 width: calc(375px * 100vh / 1624px); 11 aspect-ratio: 375 / 200; /* 保持宽高比 */ 12}
5.2 Grid布局
两列等分布局:
1.photo-grid { 2 display: grid; 3 /* 创建2列,每列最小0,最大1fr(等分剩余空间) */ 4 grid-template-columns: repeat(2, minmax(0, 1fr)); 5 /* 行间距32px,列间距30px */ 6 gap: 32px 30px; 7}
背景图片处理:
1/* ❌ 错误:顺序不对 */ 2.background { 3 background: url(image.jpg) no-repeat cover/100% 100%; 4} 5 6/* ✅ 正确:background属性顺序 */ 7.background { 8 /* 颜色 图片 重复 位置/大小 */ 9 background: #000 url(image.jpg) no-repeat center/cover; 10 /* 或分开写 */ 11 background-image: url(image.jpg); 12 background-repeat: no-repeat; 13 background-position: center; 14 background-size: cover; 15}
5.3 CSS作用域
1<style scoped> 2/* 使用scoped,样式只作用于当前组件 */ 3.container { 4 padding: 20px; 5} 6 7/* 深度选择器 */ 8.container :deep(.child) { 9 color: red; 10} 11 12/* 全局样式 */ 13.container :global(.global-class) { 14 font-size: 16px; 15} 16</style>
最佳实践:
- 非必要情况下,避免过度嵌套
- 平铺选择器提高性能
- 使用CSS变量统一设计系统
六、性能优化
6.1 虚拟滚动
1<template> 2 <RecycleScroller 3 class="scroller" 4 :items="items" 5 :item-size="itemHeight" 6 key-field="id" 7 > 8 <template v-slot="{ item }"> 9 <div class="item"> 10 {{ item.name }} 11 </div> 12 </template> 13 </RecycleScroller> 14</template> 15 16<script setup> 17import { RecycleScroller } from 'vue-virtual-scroller'; 18import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'; 19 20const items = ref(/* 大数据列表 */); 21const itemHeight = 80; 22</script> 23 24<style> 25.scroller { 26 height: 500px; 27} 28</style>
6.2 图片优化
Lottie动画替代GIF:
1<!-- 引入Lottie --> 2<script src="https://cdnjs.cloudflare.com/ajax/libs/lottie-web/5.12.2/lottie.min.js"></script> 3 4<div id="animation-container"></div> 5 6<script> 7// 加载动画 8const animation = lottie.loadAnimation({ 9 container: document.getElementById('animation-container'), 10 renderer: 'svg', // 性能最优 11 loop: true, 12 autoplay: true, 13 path: 'animation.json' // JSON文件路径 14}); 15 16// 控制方法 17animation.play(); 18animation.pause(); 19animation.setSpeed(1.5); 20</script>
图片格式对比:
| 格式 | 特点 | 适用场景 |
|---|---|---|
| APNG | 无损压缩,支持透明度,文件较大 | 需要透明背景的动画 |
| GIF | 有损压缩,256色限制,文件小 | 简单动画,兼容性要求高 |
| Lottie | JSON格式,矢量动画,文件极小 | 复杂交互动画,多平台 |
懒加载:
1<template> 2 <img 3 v-lazy="imageUrl" 4 :data-src="imageUrl" 5 class="lazy-image" 6 /> 7</template> 8 9<script> 10// 使用vue-lazyload 11import VueLazyload from 'vue-lazyload'; 12 13app.use(VueLazyload, { 14 preLoad: 1.3, 15 error: 'error.png', 16 loading: 'loading.gif', 17 attempt: 1 18}); 19</script>
6.3 代码分割
路由懒加载:
1// 静态导入(打包到主包) 2import HomePage from '@/views/Home.vue'; 3 4// 动态导入(按需加载) 5const AboutPage = () => import('@/views/About.vue'); 6 7// 带预加载提示 8const UserPage = () => ({ 9 component: import('@/views/User.vue'), 10 loading: LoadingComponent, 11 error: ErrorComponent, 12 timeout: 10000 13});
组件懒加载:
1<script setup> 2import { defineAsyncComponent } from 'vue'; 3 4const HeavyComponent = defineAsyncComponent(() => 5 import('./HeavyComponent.vue') 6); 7</script> 8 9<template> 10 <Suspense> 11 <template #default> 12 <HeavyComponent /> 13 </template> 14 <template #fallback> 15 <div>加载中...</div> 16 </template> 17 </Suspense> 18</template>
七、移动端开发
7.1 移动端调试
mkcert本地HTTPS:
1# 安装 2brew install mkcert nss # macOS 3choco install mkcert # Windows 4 5# 初始化本地CA 6mkcert -install 7 8# 为IP生成证书 9mkcert 192.168.0.101 10 11# 配置Vite 12// vite.config.ts 13import { defineConfig } from 'vite'; 14import fs from 'fs'; 15 16export default defineConfig({ 17 server: { 18 https: { 19 key: fs.readFileSync('./192.168.0.101-key.pem'), 20 cert: fs.readFileSync('./192.168.0.101.pem'), 21 }, 22 host: '0.0.0.0', // 允许局域网访问 23 }, 24});
手机调试:
- 电脑和手机连接同一WiFi
- 运行
pnpm run dev --host - 手机访问
http://电脑IP:端口号 - 使用浏览器开发者工具的远程调试
7.2 多端适配
WebView与Native区别:
| 特性 | WebView | Native |
|---|---|---|
| 开发速度 | 快,写网页即可 | 慢,需原生开发 |
| 性能 | 较差 | 优秀 |
| 更新 | 无需审核,热更新 | 需应用商店审核 |
| 功能 | 有限,依赖桥接 | 完整设备功能 |
| 体验 | 像网页 | 原生应用体验 |
安全区域适配:
1/* iOS安全区域 */ 2.safe-area { 3 padding-top: constant(safe-area-inset-top); 4 padding-top: env(safe-area-inset-top); 5 padding-bottom: constant(safe-area-inset-bottom); 6 padding-bottom: env(safe-area-inset-bottom); 7} 8 9/* 安卓虚拟键盘适配 */ 10.keyboard-area { 11 padding-bottom: env(safe-area-inset-bottom); 12}
折叠屏适配:
1/* 响应式断点 */ 2@media (min-width: 768px) and (max-width: 1023px) { 3 /* 平板 */ 4} 5 6@media (min-width: 1024px) { 7 /* PC/折叠屏展开状态 */ 8 .foldable-content { 9 max-width: 1200px; 10 margin: 0 auto; 11 } 12}
7.3 相机API使用
获取摄像头权限:
1const getCameraPermission = async () => { 2 try { 3 // 检查是否支持 4 if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) { 5 throw new Error('浏览器不支持摄像头或未在安全上下文(HTTPS)中运行'); 6 } 7 8 // 获取权限 9 const stream = await navigator.mediaDevices.getUserMedia({ 10 video: { 11 width: { ideal: 1920 }, 12 height: { ideal: 1080 }, 13 facingMode: 'environment' // 后置摄像头 14 }, 15 audio: false 16 }); 17 18 return stream; 19 } catch (error) { 20 console.error('获取摄像头失败:', error); 21 // 降级处理 22 return null; 23 } 24};
拍照功能:
1<template> 2 <div> 3 <video ref="videoRef" autoplay playsinline></video> 4 <canvas ref="canvasRef" style="display: none;"></canvas> 5 <button @click="takePhoto">拍照</button> 6 <img :src="photoUrl" v-if="photoUrl" /> 7 </div> 8</template> 9 10<script setup> 11import { ref, onMounted, onUnmounted } from 'vue'; 12 13const videoRef = ref(); 14const canvasRef = ref(); 15const photoUrl = ref(''); 16let stream = null; 17 18onMounted(async () => { 19 stream = await getCameraPermission(); 20 if (stream && videoRef.value) { 21 videoRef.value.srcObject = stream; 22 } 23}); 24 25onUnmounted(() => { 26 // 清理资源 27 if (stream) { 28 stream.getTracks().forEach(track => track.stop()); 29 } 30}); 31 32const takePhoto = () => { 33 const video = videoRef.value; 34 const canvas = canvasRef.value; 35 const context = canvas.getContext('2d'); 36 37 // 设置画布尺寸 38 canvas.width = video.videoWidth; 39 canvas.height = video.videoHeight; 40 41 // 绘制图片 42 context.drawImage(video, 0, 0, canvas.width, canvas.height); 43 44 // 转换为DataURL 45 photoUrl.value = canvas.toDataURL('image/jpeg', 0.9); 46}; 47</script>
八、调试与部署
8.1 错误监控
Sentry配置:
1// main.ts 2import * as Sentry from '@sentry/vue'; 3import { BrowserTracing } from '@sentry/browser'; 4 5Sentry.init({ 6 dsn: 'https://your-key@o0.ingest.sentry.io/project-id', 7 integrations: [new BrowserTracing()], 8 tracesSampleRate: 0.2, // 性能数据采样率 9 environment: import.meta.env.MODE, // 区分环境 10 beforeSend(event) { 11 // 过滤敏感信息 12 if (event.request?.url) { 13 delete event.request.url; 14 } 15 return event; 16 } 17});
手动捕获错误:
1// 捕获异常 2try { 3 // 可能出错的代码 4} catch (error) { 5 Sentry.captureException(error); 6 console.error('操作失败:', error); 7} 8 9// 记录消息 10Sentry.captureMessage('用户执行了XX操作', 'info');
8.2 数据埋点
基础埋点:
1// 工具函数 2export const trackEvent = (eventName: string, params = {}) => { 3 // 发送到统计平台 4 if (window.gtag) { 5 window.gtag('event', eventName, params); 6 } 7 8 // 发送到自有后端 9 fetch('/api/track', { 10 method: 'POST', 11 body: JSON.stringify({ 12 event: eventName, 13 timestamp: Date.now(), 14 ...params 15 }) 16 }).catch(() => { /* 静默失败 */ }); 17}; 18 19// 使用示例 20const handleClick = () => { 21 trackEvent('button_click', { 22 button_name: '生成按钮', 23 page: 'photo_page' 24 }); 25};
页面PV/UV统计:
1<script setup> 2import { onMounted, onUnmounted } from 'vue'; 3import { useRoute } from 'vue-router'; 4 5const route = useRoute(); 6 7onMounted(() => { 8 // 页面显示 9 trackEvent('page_view', { 10 page_path: route.path, 11 page_title: document.title 12 }); 13 14 // 页面停留时间 15 const startTime = Date.now(); 16 17 onUnmounted(() => { 18 const duration = Date.now() - startTime; 19 trackEvent('page_leave', { 20 page_path: route.path, 21 duration_ms: duration 22 }); 23 }); 24}); 25</script>
8.3 部署流程
前端发版流程:
- 开发环境:
localhost:3100→ 本地开发调试 - 测试环境:
guide.qa.17u.cn→ QA测试验证 - 预发布环境:
guide.staging.17u.cn→ 灰度测试 - 生产环境:
guide.17u.cn→ 正式上线
环境变量配置:
1# .env.development 2VITE_API_BASE=http://localhost:3000 3VITE_APP_TITLE=开发环境 4 5# .env.qa 6VITE_API_BASE=https://api.qa.17u.cn 7VITE_APP_TITLE=测试环境 8 9# .env.production 10VITE_API_BASE=https://api.17u.cn 11VITE_APP_TITLE=景点导游机
构建优化:
1// vite.config.js 2export default { 3 build: { 4 // 代码分割 5 rollupOptions: { 6 output: { 7 manualChunks: { 8 vendor: ['vue', 'vue-router', 'pinia'], 9 ui: ['element-plus', 'vant'], 10 utils: ['lodash', 'dayjs', 'axios'] 11 } 12 } 13 }, 14 // 压缩优化 15 minify: 'terser', 16 terserOptions: { 17 compress: { 18 drop_console: true, // 生产环境移除console 19 drop_debugger: true 20 } 21 }, 22 // 资源处理 23 assetsInlineLimit: 4096, // 4kb以下图片转base64 24 chunkSizeWarningLimit: 1000 // 块大小警告限制 25 } 26};
九、开发工具与插件
9.1 VS Code插件推荐
| 插件 | 作用 | 备注 |
|---|---|---|
| Volar | Vue 3语法支持 | 与Vetur冲突,需禁用Vetur |
| ESLint | 代码规范检查 | 配合项目ESLint配置 |
| Prettier | 代码格式化 | 保存时自动格式化 |
| Tailwind CSS | Tailwind智能提示 | 类名补全和提示 |
| GitLens | Git增强 | 查看代码作者、历史 |
| TODO Highlight | TODO高亮 | 配合xm-snippets使用 |
| xM-Snippets | 代码片段 | 快速生成模板代码 |
| PostCSS | CSS转换 | 支持px转rem等 |
| Markdown Preview | Markdown预览 | 支持Mermaid图表 |
插件配置:
1// settings.json 2{ 3 "editor.formatOnSave": true, 4 "editor.codeActionsOnSave": { 5 "source.fixAll.eslint": "explicit" 6 }, 7 "eslint.validate": [ 8 "javascript", 9 "javascriptreact", 10 "typescript", 11 "typescriptreact", 12 "vue" 13 ], 14 "[vue]": { 15 "editor.defaultFormatter": "Vue.volar" 16 } 17}
9.2 浏览器扩展
FeHelper前端助手:
- 二维码生成器
- 编码/解码工具
- 页面性能分析
- JSON格式化
Mock数据工具:
1// Mock配置示例 2Mock.mock('/api/user', { 3 'code': 200, 4 'data': { 5 'id': 1, 6 'name': '@cname', 7 'email': '@email', 8 'avatar': '@image' 9 } 10});
Lighthouse性能检测:
- 打开Chrome开发者工具
- 切换到Lighthouse面板
- 选择检测项目(Performance, Accessibility等)
- 生成报告并优化
9.3 其他工具
curl调试接口:
1# GET请求 2curl -X GET "https://api.example.com/users" 3 4# POST请求(JSON) 5curl -X POST "https://api.example.com/users" \ 6 -H "Content-Type: application/json" \ 7 -d '{"name":"张三","age":25}' 8 9# 带Header 10curl -H "Authorization: Bearer token" "https://api.example.com/data" 11 12# 下载文件 13curl -O https://example.com/file.zip
Postman替代方案:
1# 使用VSCode REST Client插件 2# 创建 .http 文件 3GET https://api.example.com/users 4Content-Type: application/json 5 6### 7POST https://api.example.com/users 8Content-Type: application/json 9 10{ 11 "name": "李四" 12}
十、常见问题与解决方案
10.1 Vue常见问题
问题1:$el在什么时候可用?
1// ❌ 错误:在setup或created中访问$el 2setup() { 3 console.log(this.$el); // undefined 4 onMounted(() => { 5 console.log(this.$refs.myDiv); // ✅ DOM元素 6 }); 7} 8 9// ✅ 正确:在mounted后访问 10onMounted(() => { 11 console.log(document.getElementById('my-div')); // ✅ 12 console.log(ref.value); // ✅ 13});
问题2:NextTick的作用
1// 场景:DOM更新后操作 2const handleClick = async () => { 3 showModal.value = true; 4 5 // ❌ 错误:立即操作DOM 6 document.getElementById('modal').focus(); 7 8 // ✅ 正确:等待DOM更新 9 await nextTick(); 10 document.getElementById('modal').focus(); 11};
问题3:热重载失效
1// 检查vite.config.js 2export default { 3 server: { 4 hmr: true, // 确保开启 5 watch: { 6 usePolling: true // 某些环境需要 7 } 8 } 9};
10.2 网络与证书问题
SSL证书错误:
1# 1. 检查服务器协议 2# 终端显示:http://localhost:3100/ 3# 浏览器访问:https://localhost:3100/ ❌ 4# 应该访问:http://localhost:3100/ ✅ 5 6# 2. 临时跳过(仅开发环境) 7# Chrome中页面输入:thisisunsafe 8# 或点击页面输入:badidea 9 10# 3. 配置本地HTTPS(见7.1章节)
CDN路径处理:
1// 工具函数 2window.__toCdnUrl = function(filename) { 3 // 开发环境:本地路径 4 if (import.meta.env.DEV) { 5 return `/${filename}`; 6 } 7 // 生产环境:CDN路径 8 return [`${import.meta.env.VITE_CDN_URL}/${filename}`](https://xplanc.org/primers/document/zh/10.Bash/90.%E5%B8%AE%E5%8A%A9%E6%89%8B%E5%86%8C/EX.env.md); 9}; 10 11window.__toOriginUrl = function(filename) { 12 return `${window.location.origin}/${filename}`; 13};
10.3 跨端开发
判断运行环境:
1const isWeChat = /MicroMessenger/i.test(navigator.userAgent); 2const isAlipay = /AlipayClient/i.test(navigator.userAgent); 3const isWebView = /(WebView|iPhone|iPad|iPod|Android).*AppleWebKit/i.test(navigator.userAgent); 4const isIOS = /iPhone|iPad|iPod/i.test(navigator.userAgent); 5const isAndroid = /Android/i.test(navigator.userAgent); 6 7// 使用 8if (isWeChat) { 9 // 微信环境逻辑 10} else if (isWebView) { 11 // App内WebView逻辑 12 // 顶部安全区域适配 13 document.documentElement.style.setProperty('--safe-top', '44px'); 14}
WebView与Native通信:
1// 判断是否在WebView中 2const isInWebView = () => { 3 return window.__bridge__ || 4 window.webkit?.messageHandlers || 5 navigator.userAgent.includes('WebView'); 6}; 7 8// 调用Native方法 9const callNative = (method, params = {}) => { 10 if (window.__bridge__ && window.__bridge__[method]) { 11 return window.__bridge__[method](JSON.stringify(params)); 12 } 13 14 if (window.webkit?.messageHandlers?.[method]) { 15 window.webkit.messageHandlers[method].postMessage(params); 16 return Promise.resolve(); 17 } 18 19 // 降级处理 20 console.warn('Native方法不存在,使用H5方案'); 21 return Promise.reject('非App环境'); 22}; 23 24// 使用示例 25callNative('share', { 26 title: '分享标题', 27 url: window.location.href 28}).then(() => { 29 console.log('分享成功'); 30}).catch(() => { 31 // H5分享逻辑 32});
📚 学习资源推荐
官方文档
- Vue 3 官方文档
- Vue Router 4
- Pinia 状态管理
- Vite 构建工具
- TypeScript 手册
社区资源
- Vue.js 官方论坛
- Vue 中文社区
- GitHub Vue Trending
- Stack Overflow Vue标签
工具网站
- Vue SFC Playground- 在线Vue组件编辑
- TypeScript Playground- 在线TS编译
- Can I Use- 浏览器兼容性查询
- CSS Tricks- CSS技巧和教程
-
学习路径
- 基础阶段:Vue3语法、组件通信、路由、状态管理
- 进阶阶段:TypeScript、工程化、性能优化
- 实战阶段:移动端适配、跨端开发、监控埋点
- 架构阶段:微前端、服务端渲染、低代码平台
调试技巧
- 控制台技巧:
1// 条件断点 2console.log({ variable1, variable2 }); // 对象展开 3console.table(array); // 表格显示 4console.time('timer'); console.timeEnd('timer'); // 性能测量
- Vue开发者工具:
- 组件树查看
- 状态跟踪
- 事件监视
- 性能分析
- 网络请求分析:
- 使用Filter过滤请求
- 查看请求/响应头
- 模拟慢速网络
- 断点调试XHR
《Vue3开发 First Internship》 是转载文章,点击查看原文。

