创建基于 Vue3 且将 request.js(请求封装)与页面 .vue 解耦的项目

创建基于 Vue3 且将 request.js(请求封装)与页面 .vue 解耦的项目,核心思路是把请求逻辑抽离为独立模块,页面仅调用封装好的接口方法,而非直接操作 axios 或请求配置。以下是完整的实现步骤:

一、基础项目搭建

先通过 vite 快速创建 Vue3 项目:

bash 复制代码
# 初始化项目
npm create vite@latest my-vue3-project -- --template vue
cd my-vue3-project
npm install

# 安装核心依赖(axios必装)
npm install axios  # 网络请求库
npm install pinia  # 可选,状态管理(非必须,但解耦更彻底)
npm install js-cookie  # 可选,处理token(如登录态)

找一个空目录,输入cmd:

用 Vite 创建 Vue3 项目的初始化流程,当前提示是需要先安装create-vite@8.2.0这个工具包,输入y

这是 Vite 创建项目时的实验性工具选择提示,意思是 "是否使用rolldown-vite(实验性版本)?"。

rolldown-vite是 Vite 的实验性打包工具(替代默认的 Rollup),目前还处于测试阶段,稳定性不如正式版;

这里默认选了No(不使用),这是更稳妥的选择(适合大多数项目)。

直接按回车键确认选择No即可,后续会进入正常的 Vue 项目创建流程(比如选择 Vue 版本、是否用 TypeScript 等)。

这个提示是问你:"是否现在用 npm 安装依赖并启动项目?"

选Yes:系统会自动执行npm install安装项目依赖,然后启动开发服务器(可以直接打开项目页面);

选No:只创建项目文件,不自动安装依赖和启动,后续需要手动执行npm install和npm run dev。

如果是刚创建项目,直接按回车选Yes更方便,能快速进入开发环境。

这个报错是 端口 5173 被系统拒绝访问(权限不足),导致 Vite 开发服务器启动失败,具体原因是:

Error: listen EACCES: permission denied :::5173:5173 是 Vite 默认的开发端口,但当前系统用户没有使用这个端口的权限(常见于 Windows 系统的权限限制,或端口被其他程序占用)。

方法 :换一个端口启动项目

先进入项目目录:

bash 复制代码
cd my-vue3-project

用指定端口启动(比如换 8080 端口):

bash 复制代码
npm run dev -- --port 8080


问题原因:

  • 默认端口差异 :Vite(Vue 3 官方构建工具)默认使用端口 5173,而不是 Vue CLI(Vue 2 时代)的默认端口
    8080;
  • IPv6 权限问题 :错误信息 Error: listen EACCES: permission denied ::1:5173 表明在IPv6 地址 ::1 (即 localhost 的 IPv6 版本)上使用端口 5173 时遇到权限问题。

解决方法:

修改 vite.config.js 文件,添加服务器配置:

bash 复制代码
// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path' // 需导入path模块

export default defineConfig({
  plugins: [vue()],
  resolve: {
    // 配置@别名(确保映射到src目录)
    alias: {
      '@': path.resolve(__dirname, 'src')
    }
  },
  server: {
    port: 8080 // 可选:固定端口,避免自动切换
  }
})

二、目录结构设计(解耦请求层)

推荐的目录结构(按 "请求封装→接口管理→页面调用" 分层):

src/

├── api/ # 请求层(与页面/路由完全隔离)

│ ├── request.js # axios封装(拦截器、基础配置)

│ ├── modules/ # 业务接口模块(用户、商品)

│ │ ├── user.js

│ │ └── goods.js

│ └── index.js # 接口统一导出

├── router/ # 路由层(管理页面跳转,不碰请求逻辑)

│ └── index.js # 路由配置(映射路径→页面组件)

├── views/ # 页面层(仅调用api+配合路由,不写请求/路由配置)

│ ├── Login.vue # 登录页面

│ ├── GoodsList.vue # 商品列表页面

│ ├── GoodsDetail.vue # 商品详情页面

│ └── Home.vue # 首页

├── App.vue # 根组件(放路由出口)

└── main.js # 入口文件(挂载路由+全局配置)

三、路由依赖

  • 步骤 1:安装路由依赖

首先安装 Vue Router(Vue3 配套的路由库):

bash 复制代码
npm install vue-router@4
  • 步骤 2:配置路由(router/index.js)

路由层只负责 "路径→页面" 的映射,不涉及任何请求逻辑,保证与请求层解耦:

bash 复制代码
// src/router/index.js
import { createRouter, createWebHistory } from 'vue-router'

// 替换原有静态导入 → 改为懒加载(按需导入)
// 首页/登录页可保留静态导入(首屏核心页面),非首屏页面懒加载
import Login from '@/views/Login.vue'
import Home from '@/views/Home.vue'
// 商品相关页面改为懒加载(访问时才加载)
const GoodsList = () => import('@/views/GoodsList.vue')
const GoodsDetail = () => import('@/views/GoodsDetail.vue')

// 路由规则:路径对应页面(component值不变,因为变量名还是GoodsList/GoodsDetail)
const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home
  },
  {
    path: '/login',
    name: 'Login',
    component: Login
  },
  {
    path: '/goods/list',
    name: 'GoodsList',
    component: GoodsList // 依然用变量名,无需改
  },
  {
    path: '/goods/detail/:id',
    name: 'GoodsDetail',
    component: GoodsDetail // 依然用变量名,无需改
  }
]

// 创建路由实例
const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes
})

// 路由守卫保持不变
router.beforeEach((to, from, next) => {
  const isLogin = localStorage.getItem('token')
  if (to.path !== '/login' && !isLogin) {
    next('/login')
  } else {
    next()
  }
})

export default router
  • 步骤 3:挂载路由到项目(main.js)
bash 复制代码
// src/main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router' // 导入路由

const app = createApp(App)
app.use(router) // 挂载路由
app.mount('#app')
  • 步骤 4:根组件添加路由出口(App.vue)
bash 复制代码
<template>
  <!-- 路由出口:所有页面都会渲染到这里 -->
  <router-view></router-view>
</template>

<script setup>
// 根组件仅放路由出口,无任何业务逻辑
</script>

四、封装 request.js(基础请求配置)

在 src/api/request.js 中封装 axios,包含请求拦截、响应拦截、错误处理、基础配置,与页面完全解耦:

bash 复制代码
// src/api/request.js
import axios from 'axios'
import { ElMessage } from 'element-plus'
import router from '@/router' // 仅在拦截器中跳转路由,不影响核心请求逻辑

const service = axios.create({
  baseURL: import.meta.env.VITE_API_BASE_URL,
  timeout: 10000
})

// 请求拦截器:添加token(与路由无关)
service.interceptors.request.use(
  (config) => {
    const token = localStorage.getItem('token')
    if (token) config.headers.Authorization = `Bearer ${token}`
    return config
  },
  (error) => Promise.reject(error)
)

// 响应拦截器:统一错误处理(仅在401时跳登录页,路由仅作为跳转工具)
service.interceptors.response.use(
  (res) => res.data,
  (error) => {
    if (error.response?.status === 401) {
      ElMessage.error('登录失效,请重新登录')
      localStorage.removeItem('token')
      router.push('/login') // 仅跳转,不写其他路由逻辑
    }
    ElMessage.error(error.message || '请求失败')
    return Promise.reject(error)
  }
)

export default service

解析 import axios from 'axios' 这行代码,需要导入 axios 库,Vite 安装 axios:

bash 复制代码
# 安装axios(版本兼容Vue3/Vite)
npm install axios --save

request.js 还导入了 element-plus(ElMessage),如果没装这个库,后续也会报同样的导入错误,建议一起安装:

bash 复制代码
# 安装element-plus(Vue3的UI库,提供ElMessage提示)
npm install element-plus --save

同时,需要在 main.js 中全局注册 element-plus(否则 ElMessage 用不了):

bash 复制代码
// src/main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
// 新增:导入element-plus
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css' // 导入样式

const app = createApp(App)
app.use(router)
app.use(ElementPlus) // 注册element-plus
app.mount('#app')

五、按业务模块拆分接口(解耦请求逻辑与页面)

在 src/api/modules/ 下按业务拆分接口,每个模块仅调用 request.js 的实例,不直接暴露给页面:

示例 1:用户模块接口(src/api/modules/user.js)

bash 复制代码
// src/api/modules/user.js
import request from '../request'

// 登录接口
export const login = (params) => {
  return request({
    url: '/user/login', // 接口路径(拼接baseURL)
    method: 'post',
    data: params // 登录参数(账号、密码)
  })
}

// 获取用户信息
export const getUserInfo = () => {
  return request({
    url: '/user/info',
    method: 'get'
  })
}

示例 2:商品模块接口(src/api/modules/goods.js)

bash 复制代码
// src/api/modules/goods.js
import request from '../request'

// 商品列表接口(仅请求逻辑,不碰路由/页面)
export const getGoodsList = (params) => {
  return request({
    url: '/goods/list',
    method: 'get',
    params
  })
}

// 商品详情接口
export const getGoodsDetail = (id) => {
  return request({
    url: `/goods/detail/${id}`,
    method: 'get'
  })
}

六、统一导出所有接口(页面仅需导入这个文件)

在 src/api/index.js 中汇总所有业务模块的接口,页面只需导入此文件,无需关心具体请求路径 / 方法:

bash 复制代码
// src/api/index.js
// 导入所有业务模块接口
import * as userApi from './modules/user'
import * as goodsApi from './modules/goods'

// 统一导出(页面使用时:import { userApi, goodsApi } from '@/api')
export {
  userApi,
  goodsApi
}

七、页面.vue 中调用接口(完全解耦,仅调用方法)

页面组件中不写任何 axios 配置,仅导入 api/index.js 中的方法,实现请求逻辑与页面解耦:

示例 1:登录页面(Login.vue)→ 调用接口 + 路由跳转

bash 复制代码
<template>
  <div class="login">
    <input v-model="form.username" placeholder="账号" />
    <input v-model="form.password" type="password" placeholder="密码" />
    <button @click="handleLogin">登录</button>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import { useRouter } from 'vue-router' // 导入路由钩子
import { userApi } from '@/api' // 导入请求接口(不碰request.js)

const router = useRouter()
const form = ref({ username: '', password: '' })

// 登录逻辑:仅调用接口+路由跳转,无请求配置
const handleLogin = async () => {
  try {
    const res = await userApi.login(form.value)
    localStorage.setItem('token', res.token)
    router.push('/') // 登录成功跳首页(仅跳转,不配置路由规则)
  } catch (err) {
    console.error('登录失败:', err)
  }
}
</script>

示例 2:商品列表页面(GoodsList.vue)→ 调用接口 + 跳转详情页

bash 复制代码
<template>
  <div class="goods-list">
    <div v-for="item in goodsList" :key="item.id" @click="goDetail(item.id)">
      {{ item.name }}
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { goodsApi } from '@/api' // 仅导入封装好的接口

const router = useRouter()
const goodsList = ref([])

// 加载商品列表:仅调用接口,无请求逻辑
onMounted(async () => {
  const res = await goodsApi.getGoodsList({ page: 1, size: 10 })
  goodsList.value = res.list
})

// 跳商品详情页:仅传参跳转,不修改路由配置
const goDetail = (id) => {
  router.push(`/goods/detail/${id}`)
  // 或用命名路由(更规范):
  // router.push({ name: 'GoodsDetail', params: { id } })
}
</script>

示例 3:商品详情页(GoodsDetail.vue)→ 接收参数 + 调用接口

bash 复制代码
<template>
  <div class="goods-detail">
    <h1>{{ goodsInfo.name }}</h1>
    <p>{{ goodsInfo.desc }}</p>
  </div>
</template>

<script setup>
import { ref, onMounted } from 'vue'
import { useRoute } from 'vue-router' // 接收路由参数
import { goodsApi } from '@/api'

const route = useRoute()
const goodsInfo = ref({})

// 加载详情:获取路由参数+调用接口
onMounted(async () => {
  const goodsId = route.params.id // 接收路由传的商品ID
  const res = await goodsApi.getGoodsDetail(goodsId)
  goodsInfo.value = res
})
</script>

示例 4:首页(Home.vue)→ 简写

bash 复制代码
<!-- 最简单的合法 Home.vue -->
<template>
  <div>首页内容</div>
</template>

<!-- 或者只加script(无模板) -->
<script setup>
// 首页逻辑
console.log('首页加载')
</script>

八、环境变量配置(解耦不同环境的接口地址)

在项目根目录创建 .env.development(开发环境)和 .env.production(生产环境),统一管理接口基地址,避免硬编码:

.env.development

bash 复制代码
# 开发环境接口地址
VITE_API_BASE_URL = 'http://localhost:8080/api'

.env.production

bash 复制代码
# 生产环境接口地址
VITE_API_BASE_URL = 'https://prod-api.example.com/api'

九、核心解耦优势

请求逻辑集中管理:修改接口路径、添加拦截器、调整请求头,只需改 request.js 或 api/modules,无需修改所有页面;

页面专注业务逻辑:页面仅关心 "调用哪个接口、处理返回数据",不关心请求底层实现;

便于维护和复用:多个页面可复用同一接口方法,避免重复写请求代码;

环境隔离:通过环境变量切换接口地址,开发 / 生产环境无需改代码。

十、进阶优化(可选)

添加请求取消机制:防止页面切换后重复请求(如列表页快速切换页码);

接口缓存:对高频不变的接口(如商品分类)添加缓存,减少请求;

TypeScript 支持:给接口返回值定义类型,页面调用时获得类型提示;

全局加载状态:通过 Pinia 管理请求加载状态,统一控制页面 loading。

通过以上方式,request.js 与 .vue 页面完全解耦,符合 "单一职责" 原则,后期维护和扩展会非常方便。

相关推荐
来杯@Java8 小时前
学生选课管理系统(基于springboot+vue前后端分离的项目)计算机毕业设计java
java·spring boot·spring·vue·毕业设计·maven·mybatis
医疗信息化王工16 小时前
医院自律端系统——预警处置模块全栈实战(ASP.NET Core + Vue3 + Quartz 定时调度)
mysql·postgresql·vue·asp.net core·quartz
大大杰哥18 小时前
Vue2学习(1)--了解基本方法与概念
javascript·学习·vue
Agatha方艺璇1 天前
前端开发技术复习笔记
vue·bootstrap·css3·html5·web
小葛要努力2 天前
创建vue2项目
程序人生·vue
七仔啊2 天前
基于海康门禁的人员计数系统
vue
步十人3 天前
【Vue3】前置知识简单概述(包括ES6核心语法,模块化ESM以及npm基础)
arcgis·npm·vue·es6
有梦想的程序星空3 天前
【环境配置】Vue3项目离线化本地部署echarts全攻略
前端·javascript·vue·echarts
向日的葵0064 天前
vue路由(二)
前端·javascript·vue.js·vue
小妖6665 天前
Hydration completed but contains mismatches
javascript·vue·vuepress