2025-05-28

一、概述

在 Vue 应用中,组件的创建与销毁会带来一定的性能开销,并且在切换视图时,组件的状态(如表单数据、滚动位置等)很容易丢失。Vue 官方提供了 <keep-alive> 组件,用于缓存动态组件的实例,从而避免重复渲染与数据丢失。本文将从原理、常用属性、生命周期钩子、典型应用场景等方面,结合代码示例与图解,帮助你深入理解并灵活应用 keep-alive


二、keep-alive 原理

<keep-alive> 是 Vue 内置的抽象组件(abstract component),它不会渲染为实际的 DOM 节点,而是包裹其下的子组件实例,控制它们是销毁还是缓存。默认情况下,当一个被 <keep-alive> 包裹的组件从视图移除时,不会立即销毁组件实例,而是将其实例保留在内存中;当再次切换回来时,会继续复用之前的实例,保留先前的状态。

2.1 缓存流程示意图

以下使用简化的 ASCII 图示,帮助理解组件挂载与缓存的过程:

┌──────────────┐      切换视图       ┌──────────────┐
│ 组件 A 挂载    │ -----------------> │ 组件 A 缓存    │
│ (mounted)     │ <----------------- │ (cached,无销毁)│
│               │      再次显示      │               │
└──────────────┘                    └──────────────┘

┌──────────────┐      切换视图       ┌──────────────┐
│ 组件 B 挂载    │ -----------------> │ 组件 B 缓存    │
│ (mounted)     │ <----------------- │ (cached,无销毁)│
│               │      再次显示      │               │
└──────────────┘                    └──────────────┘

当未使用 <keep-alive> 时,组件 A 和组件 B 互相切换就会被销毁再重新挂载(destroyedcreatedmounted)。而包裹在 <keep-alive> 下的组件,则是第一次挂载后进入“缓存”,再次切换回来时直接复用,不重复走创建与销毁流程。


三、基础用法与示例

3.1 动态组件方式

最常见的场景是结合 <component :is="…"> 动态组件使用 <keep-alive>。例如:在页面中,通过按钮或 Tabs 切换不同组件:

<template>
  <div>
    <!-- 切换按钮 -->
    <button @click="active = 'CompA'">显示 A</button>
    <button @click="active = 'CompB'">显示 B</button>

    <!-- 将动态组件包裹在 keep-alive -->
    <keep-alive>
      <component :is="active" />
    </keep-alive>
  </div>
</template>

<script>
import CompA from './CompA.vue'
import CompB from './CompB.vue'

export default {
  components: { CompA, CompB },
  data() {
    return {
      active: 'CompA' // 初始显示 CompA
    }
  }
}
</script>
  • 启动时active='CompA',Vue 创建并挂载 CompA,同时保留其实例。
  • 切换到 CompBCompA 被移动出视图,但因为被 keep-alive 包裹,实际并不销毁,而是进入缓存;随后创建并挂载 CompB,同理也缓存。
  • 再次切换到 CompA:直接复用缓存的 CompA 实例,不重新执行 createdmounted

3.2 路由组件方式

在 Vue-Router 场景下,经常把 <router-view> 放入 <keep-alive>,为指定的路由组件实现缓存:

<template>
  <div>
    <!-- 仅缓存以下两个路由的组件 -->
    <keep-alive include="Home,About">
      <router-view />
    </keep-alive>
  </div>
</template>
  • 当路由切换到 /home 时挂载 Home.vue;切换到 /about 时挂载 About.vue。两者都会被缓存下来。
  • 切换到 /contact 时,Contact.vue 不在 include 列表内,会正常销毁,不会缓存。
注意:要使路由组件被 keep-alive 缓存,需要在组件上设置 name,如 export default { name: 'Home', … }

四、常用属性

<keep-alive> 提供了几个可选属性,用于精细控制哪些组件需要缓存、缓存上限等。

4.1 includeexclude

  • include:一个字符串或正则表达式,只有名称匹配 include 的组件才会被缓存。
  • exclude:与 include 相反,名称匹配 exclude 的组件不会被缓存。
优先级:如果同时使用 includeexcludeexclude 优先级更高,即先判断是否在排除列表内,如果命中则不缓存。
<keep-alive include="CompA,CompB" exclude="CompC">
  <component :is="active" />
</keep-alive>
  • 只有 CompACompB 会缓存;
  • CompC 则无论是否出现在 include 中,都会被排除,不缓存。

可以使用正则表达式来匹配多个组件名称:

<!-- 缓存所有名称以 Page 开头的组件 -->
<keep-alive include="/^Page.*/">
  <component :is="active" />
</keep-alive>

4.2 max

  • max:指定缓存大小上限(组件实例的最大数量)。当缓存数量超出 max 时,最早被缓存(最久未访问)的组件会被销毁并淘汰。
<keep-alive max="2">
  <component :is="active" />
</keep-alive>
  • 假设先后切换组件为 A → B → C,此时缓存中有 A, B, C 三个实例,超过了 max=2,那么最先缓存的 A 会被销毁,缓存只保留 B, C

五、生命周期钩子

当组件被 keep-alive 缓存时,会触发两个额外的生命周期钩子:activateddeactivated。它们用于替代普通组件的 mounteddestroyed 来进行针对“缓存/复用”场景下的初始化/清理操作。

  • activated:当组件被激活(从缓存中恢复)时调用。
  • deactivated:当组件被停用(移动到缓存中,但未销毁)时调用。
<!-- Example.vue -->
<template>
  <div>
    <p>示例组件。当前计数:{{ count }}</p>
    <button @click="count++">递增</button>
  </div>
</template>

<script>
export default {
  name: 'Example',
  data() {
    return {
      count: 0
    }
  },
  mounted() {
    console.log('mounted:组件初次挂载')
  },
  activated() {
    console.log('activated:组件从缓存中恢复')
  },
  deactivated() {
    console.log('deactivated:组件被缓存')
  },
  beforeDestroy() {
    console.log('beforeDestroy:组件销毁前')
  },
  destroyed() {
    console.log('destroyed:组件真正销毁')
  }
}
</script>
  • 第一次 active='Example' 时,先执行 created -> mounted
  • 切换到其他组件,此时触发 deactivated(而非 beforeDestroydestroyed)。
  • 再次切换回 Example,执行 activated
  • 如果因 maxexclude 等原因被真正销毁,则 beforeDestroy → destroyed 会被调用。

六、详细图解:keep-alive 缓存流程

下面用一张更详细的 ASCII 流程图演示 keep-alive 对组件的挂载、缓存、激活与销毁过程。

┌─────────────────────────────────────────────────────────────────┐
│                      初次渲染(active = A)                     │
│                                                                 │
│  <keep-alive>                                                    │
│    <component is="A" />  →  Vue 真正执行 new A() 实例化,并挂载     │
│  </keep-alive>                                                    │
│                                                                 │
│ 过程:                                                            │
│   1. A.created                                                   │
│   2. A.mounted                                                   │
│   3. 缓存池:{ A 实例 }                                           │
└─────────────────────────────────────────────────────────────────┘

                     切换到 B (active = B)
┌─────────────────────────────────────────────────────────────────┐
│                      缓存 A,并挂载 B                              │
│                                                                 │
│  <keep-alive>                                                    │
│    A 实例  (状态:cached, 调用 A.deactivated)                      │
│    <component is="B" />  →  new B()                                │
│  </keep-alive>                                                    │
│                                                                 │
│ 过程:                                                            │
│   1. A.deactivated                                               │
│   2. B.created                                                   │
│   3. B.mounted                                                   │
│   4. 缓存池:{ A 实例, B 实例 }                                   │
└─────────────────────────────────────────────────────────────────┘

                     再次切换到 A (active = A)
┌─────────────────────────────────────────────────────────────────┐
│                     从缓存中恢复 A,并停用 B                        │
│                                                                 │
│  <keep-alive>                                                    │
│    <component is="A" />  →  复用 A 实例,调用 A.activated           │
│    B 实例  (调用 B.deactivated)                                   │
│  </keep-alive>                                                    │
│                                                                 │
│ 过程:                                                            │
│   1. B.deactivated                                               │
│   2. A.activated                                                 │
│   3. 缓存池:{ A 实例, B 实例 }                                   │
└─────────────────────────────────────────────────────────────────┘

                     缓存数超出 max=1
┌─────────────────────────────────────────────────────────────────┐
│           假设 max=1,缓存池已有 A, B,此时挂载 C (active = C)       │
│                                                                 │
│  <keep-alive max="1">                                             │
│    A 实例 → 淘汰最早缓存 A(调用 A.beforeDestroy、A.destroyed)     │
│    B 实例 → 调用 B.deactivated                                    │
│    <component is="C" />  →  new C()                                │
│  </keep-alive>                                                    │
│                                                                 │
│ 过程:                                                            │
│   1. A.beforeDestroy                                            │
│   2. A.destroyed                                                 │
│   3. B.deactivated                                               │
│   4. C.created                                                   │
│   5. C.mounted                                                   │
│   6. 缓存池:{ B 实例, C 实例 } (A 被彻底销毁)                   │
└─────────────────────────────────────────────────────────────────┘

要点

  1. 首次挂载:组件走 createdmounted,并进入缓存池。
  2. 切换非当前组件:停用组件,走 deactivated,保留在缓存池。
  3. 再次回到缓存组件:直接复用,不重复创建,走 activated
  4. 缓存数超限:最久未访问(最先缓存)的组件会被淘汰,走 beforeDestroydestroyed

七、典型应用场景

7.1 页面/标签切换时保持状态

在多标签、Tab 或侧边栏导航的场景中,希望返回到先前的页面时,仍能保持之前的滚动位置、表单填写状态或页面数据。示例如下:

<template>
  <div>
    <!-- 顶部 Tabs -->
    <el-tabs v-model="activeTab">
      <el-tab-pane label="用户列表" name="UserList" />
      <el-tab-pane label="设置中心" name="Settings" />
    </el-tabs>

    <!-- 用 keep-alive 缓存选项卡组件 -->
    <keep-alive>
      <component :is="activeTab" />
    </keep-alive>
  </div>
</template>

<script>
import UserList from './UserList.vue'
import Settings from './Settings.vue'

export default {
  components: { UserList, Settings },
  data() {
    return {
      activeTab: 'UserList'
    }
  }
}
</script>
  • 效果:在 UserListSettings 之间切换时,UserList 组件的数据与滚动位置会保持;无需重新请求接口或重新渲染表格数据。

7.2 路由页面缓存(路由复用)

对于较大型的 SPA 应用,某些页面切换成本较高(如需要重新请求接口、重新初始化图表等),借助 <keep-alive> 可以在返回时快速响应。以 Vue-Router 为例:

// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import Home from '@/views/Home.vue'
import Dashboard from '@/views/Dashboard.vue'
import Profile from '@/views/Profile.vue'

const routes = [
  { path: '/', component: Home, name: 'Home' },
  { path: '/dashboard', component: Dashboard, name: 'Dashboard' },
  { path: '/profile', component: Profile, name: 'Profile' }
]

const router = createRouter({
  history: createWebHistory(),
  routes
})

export default router
<!-- App.vue -->
<template>
  <div id="app">
    <router-link to="/">Home</router-link>
    <router-link to="/dashboard">Dashboard</router-link>
    <router-link to="/profile">Profile</router-link>

    <!-- 仅缓存 Dashboard 和 Profile 页面 -->
    <keep-alive include="Dashboard,Profile">
      <router-view />
    </keep-alive>
  </div>
</template>
  • 在从 Dashboard 跳转到 Home,再返回时,Dashboard 依然保持之前的状态(如图表滚动、过滤条件等);
  • Home 并未被缓存,每次进入都会重新渲染。

7.3 表单/富文本编辑状态保持

在多步骤表单或富文本编辑场景下,用户填写到一半切换到其他页面,再返回时希望输入内容不丢失:

<!-- FormStep1.vue -->
<template>
  <div>
    <h3>第一步:填写个人信息</h3>
    <el-form :model="form">
      <el-form-item label="姓名">
        <el-input v-model="form.name" />
      </el-form-item>
      <!-- 更多表单项 -->
    </el-form>
  </div>
</template>
<script>
export default {
  name: 'FormStep1',
  data() {
    return {
      form: {
        name: '',
        age: null
      }
    }
  },
  activated() {
    console.log('表单组件被恢复,保留之前输入')
  },
  deactivated() {
    console.log('表单组件被切换,缓存数据')
  }
}
</script>
<!-- FormStepContainer.vue -->
<template>
  <div>
    <el-steps :active="currentStep">
      <el-step title="个人信息" />
      <el-step title="联系方式" />
      <el-step title="提交" />
    </el-steps>
    <keep-alive>
      <component :is="currentComponent" />
    </keep-alive>
    <div>
      <el-button @click="prev" :disabled="currentStep === 0">上一步</el-button>
      <el-button @click="next" :disabled="currentStep === steps.length - 1">下一步</el-button>
    </div>
  </div>
</template>

<script>
import FormStep1 from './FormStep1.vue'
import FormStep2 from './FormStep2.vue'
import FormStep3 from './FormStep3.vue'

export default {
  components: { FormStep1, FormStep2, FormStep3 },
  data() {
    return {
      steps: ['FormStep1', 'FormStep2', 'FormStep3'],
      currentStep: 0
    }
  },
  computed: {
    currentComponent() {
      return this.steps[this.currentStep]
    }
  },
  methods: {
    next() {
      if (this.currentStep < this.steps.length - 1) {
        this.currentStep++
      }
    },
    prev() {
      if (this.currentStep > 0) {
        this.currentStep--
      }
    }
  }
}
</script>
  • 使用 <keep-alive> 包裹三个步骤的组件,可以保证在切换时,表单数据和用户输入不丢失。
  • 在组件的 activateddeactivated 钩子中,可以根据需要做二次校验、重置焦点等操作。

7.4 性能优化:避免重复渲染

在一些数据量较大的页面(如后台列表、大规模图表、三维可视化)中,重复销毁与创建组件特别浪费资源。可以借助 keep-alive 缓存视图,提升切换速度与用户体验。

<template>
  <div class="dashboard-container">
    <el-menu v-model="activeMenu" @select="onSelect">
      <el-menu-item index="analytics">Analytics</el-menu-item>
      <el-menu-item index="reports">Reports</el-menu-item>
    </el-menu>
    <keep-alive>
      <component :is="currentView" />
    </keep-alive>
  </div>
</template>

<script>
import Analytics from './Analytics.vue'
import Reports from './Reports.vue'

export default {
  components: { Analytics, Reports },
  data() {
    return {
      activeMenu: 'analytics'
    }
  },
  computed: {
    currentView() {
      return this.activeMenu === 'analytics' ? 'Analytics' : 'Reports'
    }
  },
  methods: {
    onSelect(menu) {
      this.activeMenu = menu
    }
  }
}
</script>
  • 节省网络与数据请求Analytics.vue 可能会花费几秒钟加载大型图表,缓存后再次切换回来会立即显示。
  • 避免销毁重建 DOM:大规模 DOM 重建会引起卡顿,keep-alive 可使组件保留在内存中,保持绘图状态。

八、进阶应用

8.1 动态控制缓存

在某些场景下,需要根据业务逻辑动态决定是否缓存。例如:当用户选中了“开启缓存”复选框时,才缓存组件;否则每次都销毁重建。

<template>
  <div>
    <el-checkbox v-model="useCache">开启视图缓存</el-checkbox>
    <component :is="useCache ? 'keep-alive' : 'div'">
      <component :is="currentTab" />
    </component>
  </div>
</template>

<script>
import TabA from './TabA.vue'
import TabB from './TabB.vue'

export default {
  components: { TabA, TabB },
  data() {
    return {
      currentTab: 'TabA',
      useCache: true
    }
  }
}
</script>
  • useCache = true 时,<component is="keep-alive"> 等价于 <keep-alive>,会缓存 TabA/TabB
  • useCache = false 时,<component is="div"> 仅是一个 div 容器,不会触发缓存逻辑,每次切换都销毁重建。

8.2 手动清除缓存

Vue2 中可以通过 <keep-alive :include> 或动态修改 key 来控制缓存。Vue3 提供了 <KeepAlive> 对应的 JavaScript API,例如 $refs.keepAliveComponent && $refs.keepAliveComponent.cache 等(内部缓存对象),但官方并不推荐通过私有 API 操作。

更安全的方式是,通过改变组件名或修改 include 列表,让原缓存失效。例如:将组件 name 改为动态值,迫使 keep-alive 认为是新组件,重新挂载并丢弃旧缓存。

<!-- 通过唯一标识作为 name,让 keep-alive 每次都认为是新组件 -->
<keep-alive>
  <component :is="currentComponent" :key="cacheKey" />
</keep-alive>

<!-- 当需要清空缓存时,只需修改 cacheKey -->
<script>
export default {
  data() {
    return {
      currentComponent: 'MyComp',
      cacheKey: 'v1'
    }
  },
  methods: {
    clearCache() {
      this.cacheKey = 'v' + Date.now()  // 随机更新 key,清空旧缓存
    }
  }
}
</script>

九、完整示例:综合应用

下面展示一个稍微完整些的示例——基于 Vue-Router 的多页面应用,演示如何使用 keep-alive 缓存部分路由并结合生命周期钩子做额外处理。

9.1 项目目录结构

src/
├─ App.vue
├─ main.js
└─ views/
   ├─ Home.vue
   ├─ Dashboard.vue
   └─ Profile.vue
router/
└─ index.js

9.2 路由配置(router/index.js

import { createRouter, createWebHistory } from 'vue-router'
import Home from '@/views/Home.vue'
import Dashboard from '@/views/Dashboard.vue'
import Profile from '@/views/Profile.vue'

const routes = [
  { path: '/', name: 'Home', component: Home },
  { path: '/dashboard', name: 'Dashboard', component: Dashboard },
  { path: '/profile', name: 'Profile', component: Profile }
]

const router = createRouter({
  history: createWebHistory(),
  routes
})

export default router

9.3 根组件(App.vue

<template>
  <div id="app">
    <nav>
      <router-link to="/">Home</router-link> |
      <router-link to="/dashboard">Dashboard</router-link> |
      <router-link to="/profile">Profile</router-link>
    </nav>
    <!-- 只缓存 Dashboard 和 Profile -->
    <keep-alive include="Dashboard,Profile">
      <router-view />
    </keep-alive>
  </div>
</template>

<script>
export default {
  name: 'App'
}
</script>
  • include="Dashboard,Profile":只有名称为 DashboardProfile 的组件会被缓存。
  • Home 每次切换到该路由都会重新渲染。

9.4 页面组件示例

9.4.1 Dashboard.vue

<template>
  <div>
    <h2>Dashboard</h2>
    <p>这是一个会被缓存的页面。</p>
    <p>当前时间:{{ now }}</p>
    <button @click="showCount++">增加计数 ({{ showCount }})</button>
  </div>
</template>

<script>
export default {
  name: 'Dashboard',
  data() {
    return {
      now: new Date().toLocaleTimeString(),
      showCount: 0,
      timer: null
    }
  },
  mounted() {
    this.startTimer()
    console.log('Dashboard mounted')
  },
  activated() {
    this.startTimer()
    console.log('Dashboard activated')
  },
  deactivated() {
    clearInterval(this.timer)
    console.log('Dashboard deactivated')
  },
  beforeDestroy() {
    console.log('Dashboard beforeDestroy')
  },
  destroyed() {
    console.log('Dashboard destroyed')
  },
  methods: {
    startTimer() {
      this.timer = setInterval(() => {
        this.now = new Date().toLocaleTimeString()
      }, 1000)
    }
  }
}
</script>
  • 首次挂载从缓存恢复 时,mountedactivated 都会被调用,此处都启动定时器更新时间。
  • 当切换到其他路由时,走 deactivated,清除定时器,避免内存泄漏。
  • 如果组件因某些原因被销毁(如缓存淘汰),会触发 beforeDestroydestroyed

9.4.2 Profile.vue

<template>
  <div>
    <h2>Profile</h2>
    <el-form :model="form">
      <el-form-item label="用户名">
        <el-input v-model="form.username" />
      </el-form-item>
      <el-form-item label="邮箱">
        <el-input v-model="form.email" />
      </el-form-item>
    </el-form>
  </div>
</template>

<script>
export default {
  name: 'Profile',
  data() {
    return {
      form: {
        username: '',
        email: ''
      }
    }
  },
  activated() {
    console.log('Profile 组件被激活,保留之前的表单数据')
  },
  deactivated() {
    console.log('Profile 组件被停用,表单数据仍保留在缓存中')
  }
}
</script>
  • 即使从 Profile 跳转到别的页面,返回时能看到先前填写的表单内容。

9.4.3 Home.vue

<template>
  <div>
    <h2>Home</h2>
    <p>这是一个不被缓存的页面,每次都会重新加载。</p>
    <button @click="count += 1">计数 ({{ count }})</button>
  </div>
</template>

<script>
export default {
  name: 'Home',
  data() {
    return {
      count: 0
    }
  },
  mounted() {
    console.log('Home mounted,count 重置为 0')
  }
}
</script>
  • Home 因未在 include 中,被每次切换时都会重新创建与销毁,count 始终从 0 开始。

十、小结与注意事项

  1. 缓存的本质<keep-alive> 并不会生成额外的 DOM 节点,而是将组件实例缓存在内存中。
  2. 组件 name 必须唯一:要让 include/exclude 生效,需要给每个组件设置唯一的 name 属性。
  3. 合理设置 max:根据应用场景与内存开销,控制缓存大小,防止内存占用过高。
  4. 正确清理定时器与订阅:在 deactivated 钩子中清理 setIntervalsetTimeout、WebSocket、订阅等资源,避免内存泄漏。
  5. 注意 props 或 dynamic key 的变化:当组件的 key 发生改变时,会被视为全新组件,先前缓存会失效并销毁。可用作手动清缓存手段。
  6. 服务端渲染(SSR)下不支持缓存:Vue SSR 无法在服务端缓存组件实例,keep-alive 仅在客户端有效。

通过本文,你应当对 keep-alive 的原理、属性、生命周期钩子以及典型应用场景有了系统的理解。熟练运用 keep-alive,能让你的 Vue 应用在性能与用户体验上更加出色。祝你学习顺利!

2024-11-24

在现代Web开发中,Web Worker是一个强大的功能,它允许我们在后台线程中执行JavaScript代码,从而避免主线程被阻塞,提升应用性能。尤其是在处理大量计算、复杂的数据处理或文件上传下载等操作时,Web Worker能显著改善用户体验。

本文将详细介绍如何在Vue中使用Web Worker,涵盖基本概念、代码示例和实际应用。

目录

  1. 什么是Web Worker?
  2. Web Worker的基本原理
  3. 在Vue中使用Web Worker
  4. 代码示例:Vue中使用Web Worker进行数据处理
  5. 注意事项和性能优化
  6. 总结

1. 什么是Web Worker?

Web Worker是HTML5提供的一个JavaScript API,允许我们在浏览器中创建独立于主线程的后台线程来执行任务。这意味着我们可以把一些计算密集型的操作放到Web Worker中,让主线程继续处理UI渲染和用户交互,从而避免页面卡顿和性能瓶颈。

Web Worker的特点:

  • 并行处理:Worker线程独立于主线程运行,能够并行处理任务。
  • 线程间通信:主线程和Worker线程之间通过消息传递来交换数据。
  • 不访问DOM:Web Worker不能直接访问DOM,但可以通过postMessage与主线程交换数据,主线程再更新UI。

2. Web Worker的基本原理

Web Worker的工作原理比较简单,主要分为以下几个步骤:

  1. 创建Worker线程:通过new Worker('worker.js')创建一个新的Worker线程,指定执行的脚本文件。
  2. 消息传递:主线程和Worker线程之间使用postMessage发送消息,Worker线程通过onmessage监听主线程的消息,主线程通过postMessage发送数据给Worker线程。
  3. 终止Worker线程:通过terminate()方法手动终止Worker线程,或者通过close()在Worker线程内部结束线程。

3. 在Vue中使用Web Worker

在Vue中使用Web Worker并不复杂,主要有两种方式:

  • 内联Worker:直接在Vue组件中编写Worker代码。
  • 外部Worker:将Worker代码提取到单独的文件中,然后通过new Worker()加载。

使用内联Worker

Vue不直接支持内联Worker,但可以通过Blob创建内联Worker。我们将代码写入一个Blob对象,再通过URL.createObjectURL生成Worker。

使用外部Worker

把Web Worker代码单独放在一个.js文件中,然后在Vue中引入并使用。

实现方式:使用外部Worker

下面我们来看一个在Vue 3中使用外部Web Worker的完整示例。

4. 代码示例:Vue中使用Web Worker进行数据处理

步骤1:创建Worker脚本文件

首先,我们需要创建一个Worker脚本,这个脚本会在后台执行一些数据处理任务。

worker.js

// worker.js
self.onmessage = function(e) {
  const data = e.data;
  let result = 0;

  // 模拟一个计算密集型任务
  for (let i = 0; i < data.length; i++) {
    result += data[i];
  }

  // 处理完后,将结果发送回主线程
  self.postMessage(result);
};

步骤2:在Vue组件中使用Web Worker

接下来,我们在Vue组件中创建和使用Web Worker,发送数据给Worker,并接收计算结果。

App.vue

<template>
  <div id="app">
    <h1>Vue + Web Worker 示例</h1>
    <button @click="startWorker">开始计算</button>
    <p v-if="result !== null">计算结果: {{ result }}</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      result: null, // 用于存储计算结果
      worker: null, // 用于存储Worker实例
    };
  },
  methods: {
    // 创建并启动Worker
    startWorker() {
      if (this.worker) {
        this.worker.terminate(); // 先终止旧的Worker
      }

      // 创建新的Worker实例,指定外部脚本worker.js
      this.worker = new Worker(new URL('./worker.js', import.meta.url));

      // 发送数据给Worker
      const data = [1, 2, 3, 4, 5]; // 模拟需要处理的数据
      this.worker.postMessage(data);

      // 监听Worker返回的结果
      this.worker.onmessage = (e) => {
        this.result = e.data; // 接收结果
        this.worker.terminate(); // 完成后终止Worker
      };
    },
  },
};
</script>

<style>
#app {
  text-align: center;
}
button {
  padding: 10px 20px;
  font-size: 16px;
  background-color: #42b983;
  color: white;
  border: none;
  border-radius: 5px;
  cursor: pointer;
}
button:hover {
  background-color: #5b9f6b;
}
</style>

代码说明:

  1. 创建Worker实例:在startWorker方法中,我们使用new Worker()创建一个Worker,并指定Worker的脚本文件worker.js。注意,这里我们使用了new URL()来动态加载Worker脚本,这在Vue 3中是常用的做法。
  2. 发送数据:通过postMessage()将数据发送给Worker线程。在这个例子中,我们将一个简单的数字数组传递给Worker。
  3. 接收结果:Worker执行完任务后,通过postMessage将结果返回给主线程。主线程通过onmessage事件接收结果并显示在页面上。
  4. 终止Worker:任务完成后,我们通过terminate()方法终止Worker,释放资源。

步骤3:Webpack配置支持Worker

在Vue 3中,默认情况下Webpack会把Worker脚本当做一个普通的文件处理,但我们可以配置Webpack来支持Worker的加载。在Vue项目中,通常worker.js文件是放在src目录下并通过import.meta.url来动态加载。

如果使用Vue CLI或Vite创建的Vue项目,这个配置通常是开箱即用的,支持Web Worker的动态加载。

5. 注意事项和性能优化

  • 避免主线程阻塞:Web Worker使得复杂的计算任务不会阻塞主线程,从而确保UI流畅。
  • 内存管理:Worker是独立的线程,占用内存。在Worker执行完任务后,务必通过terminate()方法及时终止它,以释放内存。
  • 数据传递:通过postMessage()传递的数据会被复制,而不是共享。因此,当传递大型数据时,可能会带来性能开销。为了优化,可以考虑使用Transferable Objects,比如ArrayBuffer,来实现高效的数据传递。

6. 总结

本文介绍了在Vue 3中如何使用Web Worker来处理后台计算任务。通过Web Worker,我们能够将繁重的计算任务移到后台线程,避免阻塞主线程,从而提高应用的响应速度和用户体验。我们展示了如何在Vue组件中创建和使用Web Worker,包括创建Worker脚本、发送数据和接收结果的过程。

Web Worker的使用场景非常广泛,尤其在处理复杂数据计算、文件处理或长时间运行的任务时,它能大大提高应用的性能。希望本文能帮助你理解并顺利地在Vue项目中实现Web Worker。

2024-11-24

在Web开发中,PDF文件的预览、翻页和下载是常见的需求。Vue 3作为一个现代的前端框架,非常适合用来构建这样的功能。vue-pdf-embed是一个基于PDF.js的Vue组件,能够方便地在Vue应用中嵌入PDF文件并实现一些基本的交互功能,如翻页、缩放、下载等。

本文将详细介绍如何在Vue 3项目中使用vue-pdf-embed组件实现PDF文件的预览、翻页、下载等功能。

目录

  1. 安装vue-pdf-embed
  2. 组件化设计:实现PDF预览
  3. 实现翻页和缩放功能
  4. 添加下载按钮功能
  5. 代码示例
  6. 总结

1. 安装vue-pdf-embed

首先,你需要在Vue 3项目中安装vue-pdf-embed库。你可以通过npm或yarn来安装。

使用npm安装:

npm install vue-pdf-embed

使用yarn安装:

yarn add vue-pdf-embed

安装完成后,就可以在Vue组件中使用vue-pdf-embed来嵌入PDF文件。

2. 组件化设计:实现PDF预览

接下来,我们将在Vue 3组件中实现PDF文件的预览功能。vue-pdf-embed提供了一个简单的方式来加载和显示PDF文件。

代码示例:

<template>
  <div class="pdf-container">
    <vue-pdf-embed
      :src="pdfUrl"  <!-- PDF文件的URL -->
      :page="currentPage"  <!-- 当前页数 -->
      :scale="scale"  <!-- 设置缩放比例 -->
      @loaded="onPdfLoaded"  <!-- PDF加载完成时触发的事件 -->
    />
    <div class="pdf-controls">
      <button @click="goToPrevPage" :disabled="currentPage <= 1">上一页</button>
      <span>{{ currentPage }} / {{ totalPages }}</span>
      <button @click="goToNextPage" :disabled="currentPage >= totalPages">下一页</button>
      <button @click="downloadPdf">下载PDF</button>
    </div>
  </div>
</template>

<script>
import { ref } from 'vue';
import { VuePdfEmbed } from 'vue-pdf-embed';  // 引入vue-pdf-embed组件

export default {
  components: {
    VuePdfEmbed
  },
  setup() {
    const pdfUrl = ref('https://example.com/your-pdf-file.pdf');  // PDF文件的URL
    const currentPage = ref(1);  // 当前页数
    const totalPages = ref(0);  // 总页数
    const scale = ref(1);  // 缩放比例

    // PDF加载完成时获取总页数
    const onPdfLoaded = (pdf) => {
      totalPages.value = pdf.numPages;
    };

    // 翻到上一页
    const goToPrevPage = () => {
      if (currentPage.value > 1) {
        currentPage.value--;
      }
    };

    // 翻到下一页
    const goToNextPage = () => {
      if (currentPage.value < totalPages.value) {
        currentPage.value++;
      }
    };

    // 下载PDF文件
    const downloadPdf = () => {
      const link = document.createElement('a');
      link.href = pdfUrl.value;
      link.download = 'file.pdf';  // 设置下载文件名
      document.body.appendChild(link);
      link.click();
      document.body.removeChild(link);
    };

    return {
      pdfUrl,
      currentPage,
      totalPages,
      scale,
      onPdfLoaded,
      goToPrevPage,
      goToNextPage,
      downloadPdf
    };
  }
};
</script>

<style scoped>
.pdf-container {
  width: 100%;
  max-width: 800px;
  margin: 0 auto;
}

.pdf-controls {
  display: flex;
  justify-content: space-between;
  margin-top: 10px;
}

button {
  padding: 5px 10px;
  font-size: 14px;
  cursor: pointer;
  background-color: #007bff;
  color: white;
  border: none;
  border-radius: 5px;
}

button:disabled {
  background-color: #ddd;
  cursor: not-allowed;
}
</style>

代码说明:

  1. vue-pdf-embed:这是一个PDF渲染组件,它通过src属性来加载PDF文件,并显示在页面上。你可以将PDF文件的URL传给它,也可以是本地的PDF路径。
  2. page属性:用于控制当前显示的页数。currentPage是一个响应式变量,初始化为1,表示第一页。
  3. scale属性:设置PDF文件的缩放比例,你可以调整这个值来改变文件的显示大小。
  4. PDF翻页功能:通过goToPrevPagegoToNextPage方法,控制PDF的翻页。currentPagetotalPages用于管理当前页数和总页数。
  5. 下载功能downloadPdf方法通过动态创建<a>标签来模拟下载操作,用户点击下载按钮后,文件会开始下载。

3. 实现翻页和缩放功能

在上面的示例中,我们已经实现了翻页功能,用户可以点击“上一页”和“下一页”按钮翻动PDF文件的页码。vue-pdf-embed组件本身会自动处理缩放比例,但你可以通过改变scale值来手动调整PDF的显示大小。例如:

const scale = ref(1.5);  // 设置缩放比例为1.5倍

你可以通过动态调整scale值来实现PDF文件的缩放功能,或者为用户提供缩放按钮来控制。

4. 添加下载按钮功能

在上面的代码中,我们已经添加了一个“下载PDF”按钮,点击后会自动下载PDF文件。这里使用了<a>标签的download属性来实现下载功能。

const downloadPdf = () => {
  const link = document.createElement('a');
  link.href = pdfUrl.value;
  link.download = 'file.pdf';  // 设置下载文件名
  document.body.appendChild(link);
  link.click();
  document.body.removeChild(link);
};

当用户点击下载按钮时,我们动态创建了一个<a>标签,并通过link.click()来模拟点击,从而启动下载。

5. 图解

图1:PDF预览和控制面板

+-------------------------------------------+
|                PDF预览区                  |
|                                           |
|                                           |
|     <vue-pdf-embed>                       |
|                                           |
+-------------------------------------------+
| Prev Page | Current Page / Total Pages | Next Page | Download |
+-------------------------------------------+
  • 上方是PDF文件的预览区域,vue-pdf-embed组件将PDF文件加载并显示出来。
  • 下方是翻页按钮、当前页和总页数显示,以及下载按钮。

图2:PDF文件下载流程

  1. 点击下载按钮
  2. 生成<a>标签,并设置文件的URL和下载文件名。
  3. 模拟点击<a>标签,启动浏览器的下载行为。

6. 总结

本文介绍了如何在Vue 3中使用vue-pdf-embed组件来实现PDF文件的预览、翻页和下载功能。通过vue-pdf-embed,我们能够快速将PDF文件嵌入到Vue应用中,并通过简单的配置实现翻页、缩放、下载等交互功能。希望这篇文章能够帮助你掌握如何在Vue应用中实现PDF文件的相关操作。如果有任何问题,随时欢迎提问!

2024-11-24

在现代Web应用中,文件上传和下载是常见的需求。Minio作为一个高性能的分布式对象存储系统,常用于文件存储。本文将讲解如何在Vue应用中,通过Minio返回的URL实现文件下载。

目录

  1. Minio简介
  2. Vue中实现文件下载的基本思路
  3. 通过Minio返回的URL下载文件
  4. 代码示例
  5. 总结

1. Minio简介

Minio是一个开源的对象存储服务,兼容Amazon S3 API,可以用来存储海量的非结构化数据,如图片、视频、文档等。它支持通过HTTP/HTTPS协议访问文件,通常通过生成带有访问权限的URL来进行文件下载。

2. Vue中实现文件下载的基本思路

在前端应用中,文件下载通常有两种方式:

  • 直接链接下载:用户点击链接,浏览器会自动开始下载。
  • 动态请求下载:通过JavaScript生成请求,获取文件流并进行处理。

Minio返回的URL可以是一个预签名的链接,这意味着你可以通过该链接直接下载文件或通过API请求进行下载。

3. 通过Minio返回的URL下载文件

假设你的Minio服务器已经配置好了,并且返回了一个有效的文件URL。我们可以使用Vue结合浏览器的<a>标签或者Blob对象来下载文件。

步骤:

  1. 获取Minio返回的URL:通常,Minio返回的URL是通过API生成的预签名URL,允许在指定时间内访问文件。
  2. 创建下载功能:在Vue中,点击按钮或链接时,使用JavaScript发起下载请求。

4. 代码示例

以下是一个简单的Vue组件,通过Minio的URL下载文件。

代码结构

<template>
  <div>
    <button @click="downloadFile">下载文件</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      fileUrl: 'https://your-minio-server.com/your-file-url', // 这是Minio返回的文件URL
    };
  },
  methods: {
    downloadFile() {
      const url = this.fileUrl;
      
      // 使用a标签模拟下载
      const link = document.createElement('a');
      link.href = url;
      link.download = url.split('/').pop(); // 提取文件名
      document.body.appendChild(link);
      link.click();
      document.body.removeChild(link);
    }
  }
};
</script>

<style scoped>
button {
  padding: 10px 20px;
  font-size: 16px;
  background-color: #007bff;
  color: white;
  border: none;
  border-radius: 5px;
  cursor: pointer;
}
button:hover {
  background-color: #0056b3;
}
</style>

代码说明:

  1. fileUrl: 这是你从Minio服务器获得的文件URL,可能是一个预签名的URL,包含了对文件的访问权限。
  2. downloadFile方法: 当用户点击“下载文件”按钮时,downloadFile方法会被触发。我们使用JavaScript动态创建了一个<a>标签,并设置其href为文件的URL,download属性为文件名。然后,通过link.click()模拟点击实现文件下载。
  3. 动态创建链接: 这种方法避免了页面刷新或跳转,直接在前端实现文件下载。

提示:

  • link.download用于指定文件下载时的默认文件名。通过url.split('/').pop()可以从URL中提取文件名。
  • 确保Minio服务器正确配置了文件的访问权限,否则下载可能会失败。

5. 图解

图1:文件下载流程图

用户点击下载按钮 → Vue组件触发downloadFile方法 → 创建下载链接(<a>标签) → 模拟点击下载文件

图2:Minio预签名URL生成过程

  1. 上传文件到Minio:通过Minio的API或客户端上传文件。
  2. 生成预签名URL:使用Minio的API生成一个带有效期的预签名URL,允许访问存储在Minio上的文件。
  3. 返回URL给前端:将该URL传递给前端,前端通过这个URL进行文件下载。

总结

本文介绍了如何在Vue中通过Minio返回的URL实现文件下载。我们通过动态创建<a>标签,并设置其download属性来模拟下载操作。通过这种方式,可以方便地在前端实现与Minio存储的交互,支持大文件下载和分布式存储。

希望这篇文章对你有所帮助,如果有任何问题,可以随时提问!

2024-09-09

由于提供的信息不足以明确指出一个特定的shell脚本或批处理文件,我将给出一个示例,展示如何使用Shell脚本和批处理文件自动化部署一个Vue前端项目。

Vue项目的自动化部署Shell脚本 (deploy.sh):




#!/bin/bash
# 自动化部署Vue前端项目脚本
 
# 停止在错误时继续执行脚本
set -e
 
# 配置
REPO_URL="https://github.com/username/repo.git"
REMOTE_HOST="user@server.com"
REMOTE_DIR="/var/www/project"
 
# 克隆仓库
git clone $REPO_URL $REMOTE_DIR
 
# 进入远程目录
cd $REMOTE_DIR
 
# 安装依赖
npm install
 
# 构建项目
npm run build
 
# 发布到服务器
rsync -av --delete dist/ $REMOTE_HOST:$REMOTE_DIR/public
 
# 通知服务器进行更新
ssh $REMOTE_HOST "cd $REMOTE_DIR && pm2 restart ecosystem.config.js"

.bat批处理文件 (deploy.bat):




@echo off
REM Windows批处理文件用于部署Vue前端项目
 
REM 配置
SET REPO_URL=https://github.com/username/repo.git
SET REMOTE_HOST=user@server.com
SET REMOTE_DIR=/var/www/project
 
REM 克隆仓库
git clone %REPO_URL% %REMOTE_DIR%
 
REM 进入远程目录
cd /d %REMOTE_DIR%
 
REM 安装依赖
call npm install
 
REM 构建项目
call npm run build
 
REM 发布到服务器
rsync -av --delete dist/ %REMOTE_HOST%:%REMOTE_DIR%/public
 
REM 通知服务器进行更新
ssh %REMOTE_HOST% "cd %REMOTE_DIR% && pm2 restart ecosystem.config.js"
 
pause

这两个脚本分别用于Linux和Windows系统,它们完成了克隆代码仓库、安装依赖、构建项目、同步文件到服务器以及通知服务器重启的过程。在实际部署时,需要根据实际情况配置REPO\_URL、REMOTE\_HOST、REMOTE\_DIR等参数。

海螺AI和ChatGPT是基于人工智能技术的两个产品,可以提供自然语言处理和机器学习的服务,但它们不直接提供部署脚本。如果需要使用海螺AI或ChatGPT来生成部署脚本,需要先与它们对接,发送相应的指令或查询,然后按照它们返回的结果进行操作。

请注意,在实际部署中,你可能还需要考虑其他安全性和权限问题,例如使用SSH密钥进行无密码登录,或者限制SSH访问等。

2024-09-09

这是一个家教管理系统的需求,它包含了前后端的技术栈。前端使用了Vue.js和Element UI,后端使用了Spring Boot和MyBatis。

首先,我们需要定义一些接口,这些接口将会被前端调用,并且需要与后端进行数据的交互。

例如,我们可以创建一个管理员登录的接口:




@RestController
@RequestMapping("/api/v1/admin")
public class AdminController {
 
    @Autowired
    private AdminService adminService;
 
    @PostMapping("/login")
    public ResponseResult login(@RequestBody Admin admin, HttpSession session) {
        return adminService.login(admin, session);
    }
}

在这个接口中,我们使用了@RestController@RequestMapping注解来定义控制器和路由信息,使用@PostMapping注解来定义一个POST请求的接口,并且使用@RequestBody注解来接收前端传递的数据。

然后,我们需要在Service层处理具体的业务逻辑:




@Service
public class AdminService {
 
    @Autowired
    private AdminMapper adminMapper;
 
    public ResponseResult login(Admin admin, HttpSession session) {
        Admin adminDB = adminMapper.selectByName(admin.getName());
        if (adminDB != null && adminDB.getPassword().equals(admin.getPassword())) {
            session.setAttribute("admin", adminDB);
            return ResponseResult.SUCCESS("登录成功");
        }
        return ResponseResult.FAILED("登录失败");
    }
}

在Service层,我们使用了@Service注解来定义一个服务,并且使用@Autowired注解来自动注入Mapper层的对象。

最后,我们需要在Mapper层定义数据库的操作:




@Mapper
public interface AdminMapper {
 
    @Select("SELECT * FROM admin WHERE name = #{name}")
    Admin selectByName(@Param("name") String name);
}

在Mapper层,我们使用了@Mapper注解来定义一个MyBatis的Mapper接口,并且使用@Select注解来定义SQL语句。

以上就是一个简单的登录接口的定义过程,其他的接口定义也可以参照这个方式来进行。

注意:这只是一个接口定义的例子,实际项目中可能会涉及到更多的接口和更复杂的业务逻辑。

2024-09-09

人人开源的renren-security是一个基于Spring Boot、Vue 3和Element Plus等框架开发的权限管理平台。以下是如何使用renren-security的基本步骤:

  1. 克隆代码仓库:



git clone https://github.com/daxianplay/renren-security.git
  1. 导入数据库:

    将项目中/db目录下的renren_security.sql文件导入到MySQL数据库中。

  2. 修改配置文件:

    renren-security-dev模块中的application-dev.yml配置文件中,修改数据库连接信息。

  3. 启动项目:

    使用IDE(如IntelliJ IDEA或Eclipse)打开项目后,启动RenrenApplication类。

  4. 访问项目:

    启动完成后,访问http://localhost:8080,使用默认账号密码admin/123456登录系统。

注意:确保你的开发环境中已安装了Java、MySQL、Node.js以及相关的构建工具。

由于renren-security是一个完整的项目,包含前后端代码,因此你可以直接运行并体验。如果你想了解其中的权限管理功能,可以参考其代码,学习其中的设计和实现。

2024-09-09



<template>
  <div>
    <input type="file" @change="handleFileChange" />
    <button @click="uploadFile">上传</button>
  </div>
</template>
 
<script setup>
import { ref } from 'vue';
 
const selectedFile = ref(null);
 
const handleFileChange = (event) => {
  selectedFile.value = event.target.files[0];
};
 
const uploadFile = async () => {
  if (!selectedFile.value) {
    alert('请选择一个文件');
    return;
  }
 
  const formData = new FormData();
  formData.append('file', selectedFile.value);
 
  try {
    const response = await fetch('YOUR_API_ENDPOINT', {
      method: 'POST',
      body: formData,
    });
    if (response.ok) {
      alert('上传成功');
    } else {
      alert('上传失败');
    }
  } catch (error) {
    alert('上传异常');
  }
};
</script>

这段代码展示了如何在Vue 3中使用Composition API实现文件的选择和上传功能。用户可以通过点击input选择文件,然后点击button进行上传。上传操作使用了fetch API和FormData对象。这个例子简单易懂,适合作为学习Vue文件上传功能的参考。

2024-09-09

由于提供的信息较为模糊,并未提供具体的源代码或者查询问题,我将提供一个简单的使用Spring Boot和Vue.js创建多租户SaaS应用的示例。

技术栈:

  • Spring Boot
  • Vue.js
  • Vue-Element-Plus

简单的多租户SaaS架构示例:

  1. 认证服务(Auth Service): 用于处理用户注册、登录、权限验证等操作。
  2. 租户服务(Tenant Service): 管理租户数据,确保每个租户拥有独立的数据库或命名空间。
  3. 核心业务服务(Core Business Service): 提供核心业务逻辑,与租户数据分离。

代码示例:

认证服务(Auth Service):




@RestController
@RequestMapping("/api/auth")
public class AuthController {
    // 用户登录接口
    @PostMapping("/login")
    public ResponseEntity<?> login(@RequestBody UserLoginDto userLoginDto) {
        // 登录逻辑...
    }
 
    // 用户注册接口
    @PostMapping("/register")
    public ResponseEntity<?> register(@RequestBody UserRegisterDto userRegisterDto) {
        // 注册逻辑...
    }
}

租户服务(Tenant Service):




@Service
public class TenantService {
    // 为租户创建数据库或者命名空间
    public void createTenant(String tenantId) {
        // 创建租户逻辑...
    }
}

核心业务服务(Core Business Service):




@RestController
@RequestMapping("/api/core-business")
public class CoreBusinessController {
    // 核心业务接口
    @GetMapping("/data")
    public ResponseEntity<?> getCoreBusinessData() {
        // 核心业务逻辑...
    }
}

前端(Vue.js):




<!-- 登录页面 -->
<template>
    <div>
        <input type="text" v-model="loginForm.username" placeholder="Username" />
        <input type="password" v-model="loginForm.password" placeholder="Password" />
        <button @click="login">Login</button>
    </div>
</template>
 
<script>
export default {
    data() {
        return {
            loginForm: {
                username: '',
                password: ''
            }
        };
    },
    methods: {
        login() {
            // 发送登录请求...
        }
    }
};
</script>

以上代码仅为示例,实际应用中需要根据具体需求进行详细设计和编码。

注意: 实际SaaS应用还需要考虑更多因素,如租户数据隔离、权限管理、支付计费、部署管理等。

2024-09-09

以下是使用Vue 3和Spring Security结合JWT实现登录认证的核心代码示例。

后端(Spring Boot):




import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
 
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
 
    private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
    private final UserDetailsService userDetailsService;
    private final JwtRequestFilter jwtRequestFilter;
 
    public SecurityConfig(JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint, UserDetailsService userDetailsService, JwtRequestFilter jwtRequestFilter) {
        this.jwtAuthenticationEntryPoint = jwtAuthenticationEntryPoint;
        this.userDetailsService = userDetailsService;
        this.jwtRequestFilter = jwtRequestFilter;
    }
 
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .csrf().disable()
                .exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).and()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                .authorizeRequests()
                .antMatchers("/api/auth/**").permitAll()
                .anyRequest().authenticated();
 
        http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
    }
 
    // ... 其他配置
}

前端(Vue 3):




import axios from 'axios';
import { ElMessage } from 'element-plus';
 
const service = axios.create({
    baseURL: process.env.VUE_APP_BASE_API, // api的base_url
    timeout: 5000 // 请求超时时间
});
 
service.interceptors.request.use(
    config => {
        if (localStorage.getItem('token')) {
            config.headers['Authorization'] = 'Bearer ' + localStorage.getItem('token');
        }
        return config;
    },
    error => {
        // 请求错误处理