Vue3开发 First Internship

作者:午安~婉日期:2026/3/6

#记录在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:主配置文件,必须提交到git
  • package.bak.json:自动生成的备份文件,不要提交到git

1.3 Node模块管理演进

幽灵依赖问题:项目可以直接引用非直接依赖的包,可能导致:

  1. 间接依赖升级破坏代码
  2. 迁移到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对比

特性GitLabGitHub
部署方式支持自托管仅SaaS
CI/CD内置GitLab CIGitHub 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色限制,文件小简单动画,兼容性要求高
LottieJSON格式,矢量动画,文件极小复杂交互动画,多平台

懒加载

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});

手机调试

  1. 电脑和手机连接同一WiFi
  2. 运行 pnpm run dev --host
  3. 手机访问 http://电脑IP:端口号
  4. 使用浏览器开发者工具的远程调试

7.2 多端适配

WebView与Native区别

特性WebViewNative
开发速度快,写网页即可慢,需原生开发
性能较差优秀
更新无需审核,热更新需应用商店审核
功能有限,依赖桥接完整设备功能
体验像网页原生应用体验

安全区域适配

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 部署流程

前端发版流程

  1. 开发环境localhost:3100→ 本地开发调试
  2. 测试环境guide.qa.17u.cn→ QA测试验证
  3. 预发布环境guide.staging.17u.cn→ 灰度测试
  4. 生产环境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插件推荐

插件作用备注
VolarVue 3语法支持与Vetur冲突,需禁用Vetur
ESLint代码规范检查配合项目ESLint配置
Prettier代码格式化保存时自动格式化
Tailwind CSSTailwind智能提示类名补全和提示
GitLensGit增强查看代码作者、历史
TODO HighlightTODO高亮配合xm-snippets使用
xM-Snippets代码片段快速生成模板代码
PostCSSCSS转换支持px转rem等
Markdown PreviewMarkdown预览支持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性能检测

  1. 打开Chrome开发者工具
  2. 切换到Lighthouse面板
  3. 选择检测项目(Performance, Accessibility等)
  4. 生成报告并优化

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. 控制台技巧
1// 条件断点  
2console.log({ variable1, variable2 }); // 对象展开  
3console.table(array); // 表格显示  
4console.time('timer'); console.timeEnd('timer'); // 性能测量  
  1. Vue开发者工具
    • 组件树查看
    • 状态跟踪
    • 事件监视
    • 性能分析
  2. 网络请求分析
    • 使用Filter过滤请求
    • 查看请求/响应头
    • 模拟慢速网络
    • 断点调试XHR

Vue3开发 First Internship》 是转载文章,点击查看原文


相关推荐


【分布式组件雪花ID】
老友記2026/2/26

分布式组件雪花ID 组成时钟回拨解决方案汇总方案一:等待后重试(阻塞等待)方案二:预留回拨位(占用序列号位)1. "预留回拨位"的核心思想2. 位分配对比图3. 具体工作场景模拟正常情况(时间向前走):发生时钟回拨(时间从1000跳回999): 4. 这种方案的优缺点5. 位运算代码示意(Java) 方案三:采用"未生成ID最大上限"自动漂移方案四:外部存储兜底(依赖Redis/ZooKeeper) 组成 雪花ID(Snowflake ID)的生成规则,核心


Linux camera驱动开发(真正需要做的linux驱动开发)
嵌入式-老费2026/2/18

【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】         很多的soc厂家,在发布sdk的时候,就提供了很多的芯片驱动。这里面有推荐的ddr、norflash、nandflash、emmc、sdio wifi、eth phy、触摸芯片等等。如果不是特殊的需求,基本上使用厂家推荐的芯片、模块,就可以做功能开发了。但是还有一些场景,是需要自己去主动适配驱动的,尤其是增加功能和降低成本的时候。 1、国产芯片适配      


OpenClaw架构揭秘:178k stars的个人AI助手如何用Gateway模式统一控制12+通讯频道
iDao技术魔方2026/2/9

一句话简介:178k stars 的开源项目 OpenClaw,用一套 Gateway 架构同时接入了 WhatsApp、Telegram、Slack、Discord 等 12+ 通讯频道,还实现了 Canvas 可视化、全时语音、浏览器控制等高级功能。这篇文章将深度拆解它的架构设计,告诉你一个「个人 AI 助手」应该如何构建。 📋 目录 背景:为什么需要个人AI助手? 项目概览:178k stars的OpenClaw 核心架构:Gateway WebSocket控制平面 多频道接入:1


墨梅博客 1.3.0 发布与服务器数据备份教训 | 2026 年第 5 周草梅周报
草梅友仁2026/2/1

本文在 草梅友仁的博客 发布和更新,并在多个平台同步发布。如有更新,以博客上的版本为准。您也可以通过文末的 原文链接 查看最新版本。 前言 欢迎来到草梅周报!这是一个由草梅友仁基于 AI 整理的周报,旨在为您提供最新的博客更新、GitHub 动态、个人动态和其他周刊文章推荐等内容。 开源动态 本周依旧在开发 墨梅 (Momei) 中。 您可以前往 Demo 站试用:demo.momei.app/ 您可以通过邮箱 admin@example.com,密码momei123456登录演示用管理


RPC分布式通信(3)--RPC基础框架接口
陌路202026/1/22

一、MprpcApplication 核心职责 MprpcApplication是 RPC 框架的 “管家”,核心作用: 单例模式:全局唯一实例,避免重复初始化; 配置加载:解析 RPC 框架的配置文件(如服务器 IP、端口、日志路径、注册中心地址等); 框架初始化:启动时初始化日志、网络、注册中心等核心组件; 全局参数访问:提供接口获取配置参数(如获取服务器端口、注册中心地址); 框架销毁:程序退出时释放资源。 二、MprpcApplication 核心接


【计算机网络 | 第三篇】MAC地址与IP地址
YYYing.2026/1/14

目录 MAC地址 一、MAC地址的格式特征 二、MAC地址的获取 三、什么是ARP? 四、ARP缓存 五、RARP IP地址 一、为什么要有IP地址? 二、既然IP地址存在,那它的意义是什么? 三、那又如何表示呢? 1、IP地址的定义 2、IPv4地址的表示方法 2.1、IPv4地址的分类编址方法 2.2、IPv4地址的划分子网编址方法 2.2.1、为什么要划分子网? 2.2.2、怎么划分子网? 2.2.3、总结 2.3、IPv4地址的无分类编址方法 3、构


Rust 的 `PhantomData`:零成本把“语义信息”交给编译器
Pomelo_刘金2026/1/5

在写底层 Rust(尤其是 unsafe / 裸指针 / FFI)时,你会遇到一种常见矛盾: 运行时:你手里可能只有一个 *const T / *mut T / *mut c_void(比如外部库返回的句柄),结构体里并没有真正存放某个引用或某个类型的值。 编译期:你又希望编译器知道“我这个类型和某个生命周期/类型绑定”,从而帮你做借用检查、推导 Send/Sync、避免错误混用等。 std::marker::PhantomData<T> 就是为了解决这个问题而存在的工具。官方文档的核心定义


前端开发者使用 AI 的能力层级——从表面使用到工程化能力的真正分水岭
月亮有石头2025/12/28

很多前端开发者已经在“使用 AI”: 会问问题、会让 AI 写代码、甚至在 IDE 里和 AI 对话。 但如果这些使用方式 无法稳定地产出可运行、可验证、可回归的工程结果, 那么严格来说——其实还没有真正入门。 这篇文章想系统回答一个问题: 前端开发者“使用 AI”的能力,是有明确层级和分水岭的。 不是工具多不多,也不是模型新不新, 而是:你用 AI 的方式,决定了它在你工程体系里的角色。 把 AI 放进工程链路,用工程约束对抗幻觉,用验证与反馈逼近真实。 AI 工程化的本质,并不是让模型


Node.js 编程实战:文件读写操作
程序员爱钓鱼2025/12/19

在后端开发中,文件读写是非常常见的需求,例如日志记录、配置文件管理、上传文件处理以及数据导入导出等。Node.js 提供了内置的 fs(File System)模块,使得我们可以高效地与文件系统进行交互。理解并掌握 Node.js 的文件读写方式,是每一个 Node.js 开发者的必备基础。 一、fs 模块简介 fs 模块是 Node.js 的核心模块之一,无需额外安装即可直接使用。它提供了同步和异步两套 API,用于完成文件的创建、读取、写入、删除以及目录操作等功能。 在实际开发中,Nod


大模型 MoE,你明白了么?
吴佳浩2025/12/11

大模型 MoE,你明白了么? 最近被T4卡搞得有点抽风就多些一点关于大模型的讲解的。由浅至深的讲个透,愿天下用老旧显卡的人儿都可以远离傻*问题。 作者:吴佳浩 最后更新:2025-12-11 适用人群:大模型上下游相关从业者 ——以 Qwen2/Qwen3 为例,从入门到回家 1. 什么是 MoE(Mixture of Experts) 核心概念 MoE = 混合专家模型,它让模型由多个"专家网络"组成,每次推理只激活少量专家,从而实现: ✅ 保留大模型能力 - 总参数量大,能力强 ✅

首页编辑器站点地图

本站内容在 CC BY-SA 4.0 协议下发布

Copyright © 2026 XYZ博客