【项目搭建】
一、第一步:用 vue create 重建 Vue 项目
首先通过 Vue CLI 初始化项目,这是基础框架:
- 打开终端,执行创建命令(替换
your-project-name为你的项目名,比如inventory-system):bash
vue create your-project-name - 选择项目配置(按需求选,新手推荐 Manually select features 手动勾选核心功能):
- 勾选
Babel(必选,转译 ES6+ 语法)、CSS Pre-processors(可选,如需 Less/Sass)、Linter / Formatter(可选,代码规范检查); - 不需要勾选
Router(因为后续手动装)、Vuex/Pinia(如果项目需要状态管理再补装,暂时可忽略)。
- 勾选
- 后续按提示选择:
- CSS 预处理器:推荐
Less(Element Plus 默认支持); - 代码规范:推荐
ESLint + Standard config(通用且严格); - 配置文件存放:选
In dedicated config files(单独文件管理,更清晰)。
- CSS 预处理器:推荐
- 等待依赖安装完成,进入项目目录:
bash
cd your-project-name
二、第二步:手动安装核心依赖(按顺序来)
你提到的 router、Element Plus、图标库、axios 都需要手动安装,下面是具体命令和配置要点:
1. 安装路由管理器:vue-router(核心,页面跳转必需)
Vue 3 对应的路由版本是 vue-router@4,命令:
bash
npm install vue-router@4
# 或用 yarn
yarn add vue-router@4
安装后需简单配置路由(否则无法使用):
- 在
src目录下新建router文件夹,再新建index.js文件,写入基础路由配置:js
// src/router/index.js import { createRouter, createWebHistory } from 'vue-router' // 导入你的页面组件(比如之前的 InventoryList、InventoryAdd) import InventoryList from '../components/views/inventory/InventoryList.vue' import InventoryAdd from '../components/views/inventory/InventoryAdd.vue' const routes = [ { path: '/', // 默认路由 redirect: '/inventory-list' // 重定向到列表页 }, { path: '/inventory-list', name: 'InventoryList', component: InventoryList }, { path: '/inventory-add', name: 'InventoryAdd', component: InventoryAdd } ] const router = createRouter({ history: createWebHistory(), // 路由模式:HTML5 History 模式(无 # 号) routes }) export default router - 在
src/main.js中引入并使用路由:js
// 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') - 在
src/App.vue中添加路由出口(页面内容会渲染到这里):vue
<!-- src/App.vue --> <template> <div id="app"> <!-- 路由出口:匹配的组件会显示在这里 --> <router-view /> </div> </template>
2. 安装 UI 库:Element Plus(含组件和图标)
你提到的 “Element 图片库” 其实是 Element Plus 官方图标库,建议直接安装 Element Plus 核心包 + 官方图标包,避免版本不兼容:
bash
# 安装 Element Plus 核心包
npm install element-plus
# 安装 Element Plus 官方图标库(@element-plus/icons-vue)
npm install @element-plus/icons-vue
安装后需配置 Element Plus(全局引入或按需引入,新手推荐全局引入,简单):
- 在
src/main.js中引入 Element Plus 和样式:js
// src/main.js import { createApp } from 'vue' import App from './App.vue' import router from './router' // 引入 Element Plus import ElementPlus from 'element-plus' // 引入 Element Plus 全局样式 import 'element-plus/dist/index.css' // 引入 Element Plus 图标(按需引入或全局注册,这里演示全局注册) import * as ElementPlusIconsVue from '@element-plus/icons-vue' const app = createApp(App) // 全局注册 Element Plus 图标 for (const [key, component] of Object.entries(ElementPlusIconsVue)) { app.component(key, component) } // 使用 Element Plus 和路由 app.use(ElementPlus) app.use(router) app.mount('#app') - 测试是否生效:在
InventoryList.vue中用一个 Element Plus 组件(比如按钮),运行项目后能正常显示即配置成功:vue
<!-- src/components/views/inventory/InventoryList.vue --> <template> <el-button type="primary">测试 Element Plus 按钮</el-button> </template>
3. 安装请求库:axios(你说的 “aixon” 应该是这个,用于接口请求)
前端调用后端接口必须用请求库,axios 是 Vue 项目中最常用的,命令:
bash
npm install axios
安装后推荐简单封装(避免重复代码,方便全局使用):
- 在
src目录下新建utils文件夹,再新建request.js文件,封装 axios:js
// src/utils/request.js import axios from 'axios' // 创建 axios 实例 const request = axios.create({ baseURL: 'http://你的后端接口基础地址', // 比如 'http://localhost:3000/api' timeout: 5000 // 请求超时时间 }) // 请求拦截器(可选,比如加 token) request.interceptors.request.use( (config) => { // 示例:给每个请求加 token(如果项目需要登录) const token = localStorage.getItem('token') if (token) { config.headers.Authorization = `Bearer ${token}` } return config }, (error) => { return Promise.reject(error) } ) // 响应拦截器(可选,统一处理错误) request.interceptors.response.use( (response) => { // 只返回响应体中的 data 部分(简化使用) return response.data }, (error) => { // 统一处理错误(比如弹窗提示) console.error('请求错误:', error) return Promise.reject(error) } ) export default request - 使用方式:在组件中引入封装后的
request调用接口:vue
<!-- src/components/views/inventory/InventoryList.vue --> <script setup> import request from '../../../utils/request' // 示例:获取库存列表数据 const getInventoryList = async () => { try { const res = await request.get('/inventory/list') // 接口路径 console.log('库存列表数据:', res) } catch (error) { console.error('获取数据失败:', error) } } // 页面加载时调用 getInventoryList() </script>
三、第三步:运行项目,验证环境是否正常
所有依赖安装和配置完成后,执行启动命令:
bash
npm run serve
如果终端显示 App running at: 且没有报错,打开浏览器访问提示的地址(比如 http://localhost:8080),能看到页面和 Element Plus 组件,说明项目环境搭建成功!
补充:如果后续需要状态管理(比如复杂数据共享)
如果项目需要跨组件共享数据(比如用户信息、全局状态),可以再补装 Vue 3 推荐的状态管理库 Pinia(替代 Vuex):
bash
npm install pinia
然后在 main.js 中引入即可,配置比较简单,按需使用即可。
【文件功能目录】
一、核心业务目录(日常开发的主要工作区)
| 优先级 | 目录 / 文件 | 核心作用 | 关键文件格式 | 开发频率 |
|---|---|---|---|---|
| 1 | src/views/ | 页面级组件(对应路由的完整页面,如首页、详情页) | .vue | 极高 |
| 2 | src/components/ | 可复用组件(通用组件如 Button,业务组件如 OrderCard,被 views 引用) | .vue | 极高 |
| 3 | src/router/ | 路由配置(页面跳转规则、嵌套路由、路由守卫) | .js/.ts | 高 |
| 4 | src/store/ | 全局状态管理(Pinia/Vuex,存储跨组件共享数据如用户信息、购物车) | .js/.ts | 高 |
| 5 | src/api/ | 接口请求封装(统一管理后端 API 调用,避免重复写 axios 逻辑) | .js/.ts | 高 |
二、辅助开发目录(支撑业务实现的工具 / 资源)
| 优先级 | 目录 / 文件 | 核心作用 | 关键文件格式 | 开发频率 |
|---|---|---|---|---|
| 6 | src/utils/ | 工具函数(时间格式化、数据校验、权限判断等通用逻辑) | .js/.ts | 中 |
| 7 | src/hooks/ | 自定义钩子(Vue3 特性,封装复用逻辑如防抖、表单验证、接口请求) | .js/.ts | 中 |
| 8 | src/assets/ | 静态资源(图片、字体、图标等,会被 webpack 处理) | .png/.svg/.woff | 中 |
| 9 | src/styles/ | 全局样式(主题变量、重置样式、公共 CSS 类) | .css/.scss/.less | 低 |
三、项目配置与输出目录(环境与部署相关)
| 优先级 | 目录 / 文件 | 核心作用 | 关键文件格式 | 开发频率 |
|---|---|---|---|---|
| 10 | public/ | 纯静态资源(不被编译,直接复制到输出目录,如 index.html、favicon.ico) | .html/.ico | 低 |
| 11 | src/main.js | 项目入口文件(创建 Vue 实例、挂载根组件、引入路由 /store) | .js/.ts | 极低 |
| 12 | dist/ | 构建输出目录(执行 npm run build 生成的可部署文件) | 编译后的 .html/.js | 几乎不手动改 |
| 13 | 根目录配置文件 | - package.json:依赖管理、脚本命令- .env:环境变量(开发 / 生产地址)- vue.config.js:Vue 配置(打包、代理) | .json/.env/.js | 极低 |
关键说明:
- 核心目录(1-5)是日常开发的主战场,直接影响业务功能实现;
- 辅助目录(6-9)是 “工具库”,为核心业务提供支持,需保证通用性;
- 配置 / 输出目录(10-13)初期配置后很少修改,主要影响项目运行和部署。
Vue 项目核心目录结构(树形说明版)
plaintext
项目根目录
├─ public/ # 纯静态资源目录(不被webpack编译,直接复制到输出)
│ ├─ index.html # 项目入口HTML(Vue实例挂载的根容器)
│ └─ favicon.ico # 浏览器标签页图标
├─ src/ # 项目源代码根目录(开发核心工作区)
│ ├─ assets/ # 静态资源(会被webpack处理,可被组件引用)
│ │ ├─ images/ # 图片资源(如logo.png、背景图)
│ │ └─ styles/ # 全局样式(如reset.css、主题变量.scss)
│ ├─ components/ # 通用可复用组件(非页面级,供views或其他组件引用)
│ │ ├─ Navbar.vue # 导航栏组件(全局通用,如顶部菜单)
│ │ └─ Pagination.vue # 分页组件(列表页通用,如用户列表分页)
│ ├─ views/ # 页面级组件(对应路由,每个文件是一个完整页面)
│ │ ├─ Home.vue # 首页组件(路由路径:/)
│ │ ├─ About.vue # 关于页组件(路由路径:/about)
│ │ └─ UserList.vue # 用户列表页组件(路由路径:/user/list,含表单+列表)
│ ├─ router/ # 路由配置目录(管理URL与页面组件的映射)
│ │ └─ index.js # 路由规则核心文件(定义routes数组、创建路由实例)
│ ├─ api/ # 接口请求目录(统一管理后端API调用)
│ │ └─ user.js # 用户相关接口(如getUserList、login等方法)
│ ├─ utils/ # 工具函数目录(通用逻辑,与业务解耦)
│ │ ├─ request.js # axios封装文件(基础路径、拦截器配置)
│ │ └─ formatTime.js # 时间格式化工具(如日期转YYYY-MM-DD)
│ ├─ store/ # 全局状态管理目录(Pinia/Vuex,跨组件共享数据)
│ │ └─ userStore.js # 用户状态存储(如当前登录用户信息、token)
│ ├─ App.vue # 根组件(页面布局骨架,含<router-view>和导航)
│ └─ main.js # 项目入口JS(创建Vue实例、挂载路由/store)
├─ package.json # 项目配置文件(依赖管理、脚本命令如npm run dev)
├─ vue.config.js # Vue项目配置(如端口号、代理、打包配置)
└─ .gitignore # Git忽略规则(指定不提交的文件,如node_modules、dist)
【组件是什么】
在 Vue 开发中,组件(Component)是构成页面的最小功能单元,可以理解为 “可复用、可组合的代码片段”—— 它把 HTML 结构、CSS 样式、JavaScript 逻辑封装在一起,像搭积木一样,能重复使用在不同页面,也能组合成更复杂的页面。
一、组件的核心特点:为什么需要组件?
组件的设计核心是 **“复用” 和 “解耦”**,解决传统开发中 “代码重复写、逻辑混乱” 的问题,具体有 4 个关键特点:
- 高内聚:一个组件内,HTML(模板)、CSS(样式)、JS(逻辑)是 “打包好的”,比如一个「按钮组件」,它的外观(红色、圆角)和点击逻辑(弹窗提示)都封装在自己内部,不依赖外部代码。
- 低耦合:组件之间相互独立,修改一个组件的逻辑 / 样式,不会影响其他组件。比如改了「列表项组件」的字体颜色,不会导致「搜索框组件」出问题。
- 可复用:写一次组件,能在多个页面甚至多个项目中重复使用。比如一个「分页组件」,首页、列表页、详情页需要分页时,直接 “拿来用” 即可,不用重复写分页逻辑。
- 可组合:小组件能拼合成大小件,大小件能拼合成页面。比如:「商品卡片组件」(小)→ 「商品列表组件」(中)→ 「商品首页」(页面)。
二、Vue 组件的两种核心类型(按用途分)
Vue 中组件按 “复用范围” 和 “功能定位”,主要分为两类,对应你之前了解的 components/ 和 views/ 目录:
| 类型 | 核心用途 | 典型场景 | 存放目录 | 复用性 |
|---|---|---|---|---|
| 通用组件 | 通用 UI 或基础功能 | 按钮、输入框、弹窗、分页 | src/components/ | 高(跨页面 / 跨业务) |
| 页面组件 | 对应路由的完整页面 | 首页、商品详情页、个人中心 | src/views/ | 低(通常只在一个路由用) |
举个例子:
- 「首页(views/Home.vue)」是页面组件,它会引用多个通用组件:顶部的「导航栏(components/Navbar.vue)」、中间的「商品卡片(components/GoodsCard.vue)」、底部的「分页(components/Pagination.vue)」。
三、Vue 组件的基本结构(3 大核心部分)
一个标准的 Vue 单文件组件(.vue 格式,也是最常用的格式),内部固定包含 3 个核心标签,对应 “结构 - 样式 - 逻辑”:
vue
<!-- 1. 模板(Template):组件的 HTML 结构,决定“显示什么” -->
<template>
<!-- 组件内部的 DOM 结构,只能有一个根元素 -->
<div class="my-button">
<!-- 可以用 {{ }} 绑定数据,或 @ 绑定事件 -->
<button @click="handleClick">{{ buttonText }}</button>
</div>
</template>
<!-- 2. 脚本(Script):组件的 JavaScript 逻辑,决定“做什么” -->
<script>
// Vue 3 组合式 API 写法(主流)
import { ref } from 'vue' // 引入 Vue 工具函数
export default {
// 组件名称(调试/递归组件时有用)
name: 'MyButton',
// 组件接收的“外部参数”(父组件传值用)
props: {
buttonText: {
type: String, // 参数类型
default: '默认按钮' // 默认值
}
},
// 组件的逻辑(组合式 API 写在 setup 中)
setup(props) {
// 定义点击事件的逻辑
const handleClick = () => {
alert(`你点击了:${props.buttonText}`)
}
// 暴露方法/数据给模板使用
return { handleClick }
}
}
</script>
<!-- 3. 样式(Style):组件的 CSS 样式,决定“长什么样” -->
<style scoped>
/* scoped:样式只作用于当前组件,避免污染其他组件 */
.my-button button {
padding: 8px 16px;
background: #42b983; /* Vue 绿色 */
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
/* 鼠标悬浮效果 */
.my-button button:hover {
background: #359469;
}
</style>
关键说明:
<template>:必须有一个根元素(如<div>),否则 Vue 无法解析;<script>:通过props接收父组件传过来的数据,通过setup写逻辑;<style scoped>:scoped是 Vue 特有的属性,能让样式 “作用域隔离”,避免和其他组件的样式冲突(非常重要)。
四、组件的 “父子关系”:如何协作?
组件不是孤立的,页面中通常是 “父组件嵌套子组件”,比如「首页(父)」包含「商品卡片(子)」,它们之间通过 3 种方式协作:
-
父传子(Props):父组件给子组件传数据。
比如父组件要让子组件显示 “购买按钮”,就给子组件的buttonText传值:vue
<!-- 父组件中使用子组件 --> <template> <MyButton buttonText="立即购买" /> <!-- 传值给子组件的 props --> </template> -
子传父(Events):子组件给父组件发 “通知”。
比如子组件被点击后,要告诉父组件 “按钮被点了”,就通过emit触发事件:vue
<!-- 子组件中触发事件 --> <script> export default { setup(props, { emit }) { // emit 是 Vue 提供的“事件触发函数” const handleClick = () => { emit('button-click', props.buttonText) // 触发事件,传参数 } return { handleClick } } } </script> <!-- 父组件中监听事件 --> <template> <MyButton buttonText="立即购买" @button-click="handleButtonClick" <!-- 监听子组件的事件 --> /> </template> <script> export default { setup() { const handleButtonClick = (text) => { console.log(`父组件收到:${text} 被点击了`) } return { handleButtonClick } } } </script> -
插槽(Slots):父组件给子组件传 “HTML 内容”(比如子组件是弹窗,父组件传弹窗里的内容)。
比如子组件留一个 “插槽位”,父组件填充内容:vue
<!-- 子组件(弹窗组件)中定义插槽 --> <template> <div class="modal"> <div class="modal-content"> <!-- 插槽:父组件传的内容会放在这里 --> <slot></slot> </div> </div> </template> <!-- 父组件中填充插槽内容 --> <template> <Modal> <!-- 这里的内容会替换子组件的 <slot> --> <h3>提示</h3> <p>确定要删除这条数据吗?</p> </Modal> </template>
总结
简单来说,Vue 组件就是 “封装好的功能块”:
- 从用途看,分 “通用小组件”(按钮、弹窗)和 “页面大组件”(首页、详情页);
- 从结构看,包含 “模板(显示)、脚本(逻辑)、样式(外观)” 三部分;
- 从协作看,通过 “Props 传值、Events 传事件、Slots 传内容” 实现父子通信。
【组件案例】
一、父组件给子组件传值(Props)
场景:父组件有一个名字,需要传给子组件显示。
子组件(Child.vue)
vue
<template>
<!-- 直接使用 props 中的数据 -->
<p>子组件收到的名字:{{ userName }}</p>
</template>
<script>
export default {
// 声明接收父组件传的值(指定类型和默认值)
props: {
userName: {
type: String, // 必须是字符串类型
default: '游客' // 父组件没传值时用默认值
}
}
}
</script>
父组件(Parent.vue)
vue
<template>
<div>
<p>父组件的名字:张三</p>
<!-- 给子组件传值:v-bind:userName 简写为 :userName -->
<Child :userName="parentName" />
</div>
</template>
<script>
// 引入子组件
import Child from './Child.vue'
export default {
components: {
Child // 注册子组件才能使用
},
data() {
return {
parentName: '张三' // 父组件要传递的数据
}
}
}
</script>
效果:
父组件的 parentName 会传递给子组件,子组件显示 子组件收到的名字:张三。
二、子组件给父组件传值(Events)
场景:子组件有一个按钮,点击后把数字传给父组件,父组件显示这个数字。
子组件(Child.vue)
vue
<template>
<!-- 点击按钮时触发事件,传值给父组件 -->
<button @click="sendNumber">点击传值</button>
</template>
<script>
export default {
methods: {
sendNumber() {
// 子组件通过 $emit 触发事件,第一个参数是事件名,第二个是要传的值
this.$emit('number-change', 666)
}
}
}
</script>
父组件(Parent.vue)
vue
<template>
<div>
<p>父组件收到的数字:{{ receivedNumber }}</p>
<!-- 监听子组件的事件,用方法接收值 -->
<Child @number-change="handleNumber" />
</div>
</template>
<script>
import Child from './Child.vue'
export default {
components: {
Child
},
data() {
return {
receivedNumber: 0 // 存储子组件传来的数字
}
},
methods: {
// 子组件触发事件时,这个方法会被调用,参数就是子组件传的值
handleNumber(num) {
this.receivedNumber = num
}
}
}
</script>
效果:
点击子组件的按钮后,父组件会显示 父组件收到的数字:666。
核心总结
-
父传子:
- 子组件用
props声明接收的数据 - 父组件在使用子组件时,用
:属性名="数据"传值
- 子组件用
-
子传父:
- 子组件用
this.$emit('事件名', 数据)触发事件 - 父组件在使用子组件时,用
@事件名="方法"监听,方法参数就是子组件传的值
- 子组件用
这两种方式是 Vue 组件通信最基础也最常用的方式,逻辑清晰且易于理解。
用组件开发,能让代码更简洁、易维护 —— 比如要改所有按钮的颜色,只需要改「按钮组件」的样式,不用每个页面都改一遍。
【为什么使用路由】
第一步:最原始的跳转 ——HTML 原生 A 链接(<a> 标签)
这是网页最基础的跳转方式,没有任何框架时,我们都用它。
核心逻辑
通过 <a href="目标页面地址"> 跳转到新页面,本质是向服务器发送新的页面请求,服务器返回新 HTML 文件,浏览器销毁旧页面、加载新页面。
代码示例
html
<!-- 首页(index.html) -->
<a href="about.html">去关于我们页面</a>
<!-- 关于页(about.html) -->
<a href="index.html">回到首页</a>
优点 & 痛点
- 优点:极简单,不用写任何 JS,浏览器原生支持。
- 痛点(致命问题):
- 页面刷新:每次点击都会刷新整个页面,屏幕会闪一下,体验差。
- 资源重复加载:新页面需要重新加载 CSS、JS、图片等资源,即使和旧页面有重复(比如导航栏、页脚),也会重新加载。
- 状态丢失:旧页面的临时数据(比如表单输入的内容、滚动位置)会完全丢失。
第二步:优化刷新问题 ——JS 历史记录 API(无刷新跳转)
为了解决 “页面刷新” 的痛点,浏览器提供了 History API(pushState/replaceState),允许通过 JS 修改地址栏 URL,但不发送服务器请求、不刷新页面。
核心逻辑
- 用 JS 监听点击事件,阻止
<a>标签的默认跳转行为(避免刷新)。 - 用
history.pushState()修改地址栏 URL,同时把新 URL 存入浏览器历史记录(支持后退按钮)。 - 根据新 URL 切换页面内容(比如隐藏首页 DOM、显示关于页 DOM)。
代码示例(原生 JS 实现)
html
<!-- 页面结构:两个“页面”内容默认隐藏一个 -->
<div id="home" style="display: block;">首页内容</div>
<div id="about" style="display: none;">关于我们内容</div>
<!-- A 链接只用于触发点击,href 仅作标识 -->
<a href="#home" class="link">首页</a>
<a href="#about" class="link">关于我们</a>
<script>
// 1. 获取所有链接和页面元素
const links = document.querySelectorAll('.link');
const homePage = document.getElementById('home');
const aboutPage = document.getElementById('about');
// 2. 监听链接点击
links.forEach(link => {
link.addEventListener('click', (e) => {
// 阻止默认跳转(关键:避免刷新页面)
e.preventDefault();
// 3. 获取目标 URL(这里用 href 的值作为标识)
const targetUrl = link.getAttribute('href'); // 比如 "#home" 或 "#about"
// 4. 用 History API 修改地址栏(不发请求)
history.pushState({}, '', targetUrl); // 第三个参数是新的地址栏 URL
// 5. 切换页面内容(手动控制 DOM 显示/隐藏)
if (targetUrl === '#home') {
homePage.style.display = 'block';
aboutPage.style.display = 'none';
} else if (targetUrl === '#about') {
homePage.style.display = 'none';
aboutPage.style.display = 'block';
}
});
});
// 6. 处理浏览器“后退/前进”按钮(History API 会触发 popstate 事件)
window.addEventListener('popstate', () => {
const currentUrl = window.location.hash; // 获取当前地址栏的 # 后面的内容
if (currentUrl === '#home') {
homePage.style.display = 'block';
aboutPage.style.display = 'none';
} else if (currentUrl === '#about') {
homePage.style.display = 'none';
aboutPage.style.display = 'block';
}
});
</script>
优点 & 新痛点
- 优点:实现了 “无刷新跳转”,体验大幅提升,资源不用重复加载,页面状态也能保留。
- 新痛点(工程化问题):
- 手动控制 DOM 太麻烦:如果页面多(比如 10 个页面),要写大量
if-else控制显示 / 隐藏,代码臃肿。 - URL 与内容的映射混乱:URL 变了要手动对应到具体的页面内容,没有统一的 “规则” 管理。
- 不支持组件化:在 Vue/React 这类组件化框架中,我们需要的是 “URL 对应组件”,而不是 “URL 对应 DOM 显示 / 隐藏”。
- 手动控制 DOM 太麻烦:如果页面多(比如 10 个页面),要写大量
第三步:框架适配 ——Vue Router 的诞生(URL 对应组件)
Vue 是组件化框架,所有页面都是由组件构成的。为了解决 “URL 与组件映射” 的痛点,Vue 官方推出了 Vue Router—— 它本质是对 “History API” 的封装,核心是实现 “URL 路径 → 对应组件” 的自动映射。
核心逻辑
- 定义路由规则:告诉 Vue Router “哪个 URL 对应哪个组件”。
- 挂载路由容器:在页面中留一个 “坑”(
<router-view>),让匹配的组件自动渲染到这里。 - 用路由链接跳转:用
<router-link>代替原生<a>标签(本质还是调用 History API,但不用手动写 JS)。
代码示例(Vue 3 + Vue Router 4)
1. 安装 Vue Router
bash
npm install vue-router@4
2. 定义路由规则(router/index.js)
javascript
// 1. 引入需要的组件
import { createRouter, createWebHistory } from 'vue-router';
import Home from '../components/Home.vue'; // 首页组件
import About from '../components/About.vue'; // 关于页组件
// 2. 定义路由规则:URL 路径 → 组件
const routes = [
{ path: '/', component: Home }, // 根路径 → Home 组件
{ path: '/about', component: About } // /about 路径 → About 组件
];
// 3. 创建路由实例:用 History API 模式(无 # 号,更美观)
const router = createRouter({
history: createWebHistory(), // 对应 History API 的无刷新模式
routes // 传入路由规则
});
export default router;
3. 在 Vue 中使用路由(main.js)
javascript
import { createApp } from 'vue';
import App from './App.vue';
import router from './router'; // 引入路由实例
// 把路由挂载到 Vue 应用中
createApp(App).use(router).mount('#app');
4. 页面中使用(App.vue)
vue
<template>
<!-- 1. 路由链接:代替原生 <a>,点击不刷新 -->
<router-link to="/">首页</router-link>
<router-link to="/about">关于我们</router-link>
<!-- 2. 路由容器:匹配的组件会自动渲染到这里 -->
<router-view></router-view>
</template>
优点:解决了之前的所有痛点
- 无刷新跳转:基于 History API,体验流畅。
- 自动映射组件:URL 对应组件的规则统一管理,不用手动写
if-else控制 DOM。 - 支持组件化:完美适配 Vue 的组件化开发,每个页面就是一个组件。
- 额外能力:还支持嵌套路由(比如
/user/123)、路由参数(比如获取123)、路由守卫(权限控制)等工程化必需的功能。
总结:路由的演变逻辑
| 阶段 | 核心方式 | 解决的问题 | 暴露的新问题 |
|---|---|---|---|
| 1. 原生跳转 | <a> 标签 + 服务器请求 | 实现基本跳转 | 页面刷新、资源重复加载 |
| 2. 无刷新跳转 | History API + 手动 DOM 控制 | 解决页面刷新问题 | DOM 控制繁琐、无组件映射 |
| 3. Vue Router 跳转 | URL → 组件自动映射 | 解决组件化适配、规则管理 | 无(满足工程化需求) |
简单说:Vue Router 不是凭空出现的,而是为了适配 Vue 的组件化开发,解决 “原生跳转” 和 “手动 JS 跳转” 的工程化痛点,最终形成的一套 “URL 管理组件” 的标准方案—— 这就是为什么现在 Vue 项目都用 Vue Router 做路由。
+
步骤 1:创建页面组件(新增文件)
首先在 src/views/ 目录下新建页面组件(.vue 文件),这是你要展示的新页面内容。
示例:新建 src/views/Contact.vue(联系我们页面)
vue
<template>
<div class="contact-page">
<h2>联系我们</h2>
<p>这是新增的联系页面</p>
</div>
</template>
步骤 2:配置路由规则(修改路由文件)
在路由配置文件中添加新页面的「URL 路径 → 组件」映射规则,告诉路由 “访问哪个地址显示哪个组件”。
路由文件位置:通常是 src/router/index.js(或 src/router/routes.js)
修改内容:
javascript
运行
// 1. 先导入新建的页面组件
import Contact from '../views/Contact.vue';
// 2. 在 routes 数组中添加新路由规则
const routes = [
{ path: '/', component: Home },
{ path: '/about', component: About },
// 新增这一行:URL 为 /contact 时,显示 Contact 组件
{ path: '/contact', component: Contact }
];
步骤 3:添加跳转链接(修改导航组件)
在页面的导航区域(通常是 App.vue 或 components/Navbar.vue)添加跳转链接,让用户能点击进入新页面。
用 Vue Router 提供的 <router-link> 标签(代替原生 <a> 标签,避免页面刷新):
示例(修改 App.vue):
vue
<template>
<div>
<!-- 导航链接区域 -->
<nav>
<router-link to="/">首页</router-link> |
<router-link to="/about">关于我们</router-link> |
<!-- 新增这一行:点击跳转到 /contact 路径 -->
<router-link to="/contact">联系我们</router-link>
</nav>
<!-- 路由容器:新页面会在这里显示 -->
<router-view></router-view>
</div>
</template>
步骤 4:(可选)配置路由高亮样式(优化体验)
默认点击后的 <router-link> 会自动添加一个 router-link-active 类,可自定义高亮样式,让用户知道当前在哪个页面。
示例(在 App.vue 的 style 中添加):
vue
<style>
/* 点击后的链接高亮样式 */
.router-link-active {
color: #42b983; /* Vue 绿色 */
font-weight: bold;
text-decoration: underline;
}
</style>
总结:需要修改的文件清单
| 操作 | 文件路径 | 作用 |
|---|---|---|
| 新增文件 | src/views/Contact.vue | 定义新页面的内容 |
| 修改文件 | src/router/index.js | 添加 URL 与组件的映射规则 |
| 修改文件 | src/App.vue(或导航组件) | 添加跳转链接,让用户能访问 |
| (可选)修改文件 | 任意样式文件 | 配置路由高亮,优化视觉体验 |
完成这几步后,运行项目就能通过点击链接跳转到新页面,且整个过程无刷新,完全符合 Vue 单页应用的特性。
【为什么使用axios向后台发送请求】
一、用 <a> 标签向后端发送请求(最原始方式)
<a> 标签本质是通过 href 发起 GET 请求,常用于简单的页面跳转或获取数据(但实际开发中很少直接用它发业务请求)。
核心原理
点击 <a> 标签时,浏览器会向 href 中的 URL 发送 GET 请求,后端处理后返回响应(通常是新页面或数据)。
代码示例
html
预览
<!-- 向后端发送 GET 请求,参数拼接在 URL 后(?key=value) -->
<a href="http://localhost:3000/api/user?id=123">获取 ID=123 的用户信息</a>
特点
- 只能发 GET 请求(参数暴露在 URL 中,不安全)。
- 会 刷新页面 或跳转到新页面,无法在当前页面处理响应数据。
- 无法设置请求头、处理错误(纯浏览器行为,无 JS 控制)。
适用场景:纯静态页面的简单跳转,几乎不用在现代前端项目中。
二、用 axios 基础用法发送请求(主流基础方案)
axios 是专门处理 HTTP 请求的 JS 库,支持各种请求方式,能在当前页面处理响应,是前端项目的基础选择。
步骤 1:安装 axios
bash
npm install axios
步骤 2:基础用法示例(直接在组件中使用)
vue
<template>
<div>
<button @click="getUser">获取用户信息</button>
<p>{{ userInfo.name }}</p>
</div>
</template>
<script>
import axios from 'axios'; // 导入 axios
export default {
data() {
return {
userInfo: {}
}
},
methods: {
async getUser() {
try {
// 1. 发送 GET 请求(参数在 URL 中)
const getRes = await axios.get('http://localhost:3000/api/user', {
params: { id: 123 } // 自动拼接为 ?id=123
});
// 2. 发送 POST 请求(参数在请求体中)
const postRes = await axios.post('http://localhost:3000/api/login', {
username: 'admin',
password: '123456'
});
// 3. 处理响应数据
this.userInfo = getRes.data; // 将后端返回的数据赋值给变量
console.log('登录结果:', postRes.data);
} catch (error) {
// 4. 捕获错误(如网络异常、后端报错)
console.error('请求失败:', error.message);
}
}
}
}
</script>
特点
- 支持 GET/POST/PUT/DELETE 等所有请求方式。
- 基于 Promise,可配合
async/await写同步风格代码,清晰易读。 - 能在当前页面处理响应数据,不刷新页面。
- 可设置请求头(如 Token 认证)、超时时间等。
问题:如果每个组件都直接写 axios 调用,会有大量重复代码(如基础 URL、错误处理)。
三、封装 request.js 后的用法(工程化方案)
在实际项目中,会把 axios 封装到 request.js 中,统一重复逻辑(如基础路径、拦截器),让组件中的请求代码更简洁。
步骤 1:创建 request.js(封装 axios)
javascript
运行
// src/utils/request.js
import axios from 'axios';
// 1. 创建 axios 实例,配置基础路径
const request = axios.create({
baseURL: 'http://localhost:3000/api', // 所有请求的基础 URL(避免重复写)
timeout: 5000 // 超时时间(5秒没响应则报错)
});
// 2. 请求拦截器:发送请求前做处理(如添加 Token)
request.interceptors.request.use(
(config) => {
// 示例:给所有请求添加 Token 认证头
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => {
// 请求出错时的处理
return Promise.reject(error);
}
);
// 3. 响应拦截器:收到响应后做处理(如统一错误提示)
request.interceptors.response.use(
(response) => {
// 只返回后端的业务数据(过滤 axios 自带的包装)
return response.data;
},
(error) => {
// 统一处理错误(如 401 未登录跳转到登录页)
if (error.response?.status === 401) {
alert('请先登录');
window.location.href = '/login';
} else {
alert('请求失败:' + (error.message || '未知错误'));
}
return Promise.reject(error);
}
);
export default request; // 导出封装好的实例
步骤 2:在组件中使用 request.js
vue
<template>
<div>
<button @click="getUser">获取用户信息</button>
</div>
</template>
<script>
import request from '@/utils/request'; // 导入封装好的 request
export default {
methods: {
async getUser() {
try {
// 1. 发送 GET 请求(不用写完整 URL,自动拼接 baseURL)
const userData = await request.get('/user', {
params: { id: 123 } // 最终请求地址:baseURL + /user + ?id=123
});
// 2. 发送 POST 请求
const loginData = await request.post('/login', {
username: 'admin',
password: '123456'
});
console.log('用户信息:', userData);
console.log('登录结果:', loginData);
} catch (error) {
// 错误会被 request.js 中的响应拦截器统一处理,这里可做额外处理
console.log('请求被中断');
}
}
}
}
</script>
特点
- 减少重复代码:基础 URL、超时时间等全局配置只写一次。
- 统一拦截处理:请求前自动加 Token,响应后自动处理错误(如登录失效)。
- 更简洁的调用:组件中只需关注业务参数,不用关心底层配置。
- 便于维护:如果需要更换请求库(如从 axios 换成 fetch),只需修改
request.js,不用改所有组件。
总结:三种方式的对比
| 方式 | 核心特点 | 适用场景 |
|---|---|---|
<a> 标签 | 只能发 GET 请求,会刷新页面,无 JS 控制 | 纯静态页面跳转(几乎不用) |
| 直接用 axios | 支持各种请求,可处理响应,有重复代码 | 小型项目或临时调试 |
| 封装 request.js | 统一配置、拦截处理,代码简洁易维护 | 中大型项目(实际开发的标准方案) |
实际开发中,封装后的 request.js 是绝对主流,它既保留了 axios 的灵活性,又解决了工程化中的重复代码和统一处理问题
【案例 可以通过 传入参数 和需要返回的参数进行实操】
整体结构
我们会用到 4 个核心文件,形成清晰的分层结构:
utils/request.js:封装 axios(基础请求工具)api/user.js:统一管理用户相关接口(API 层)views/UserList.vue:页面组件(调用 API 并展示数据)- 后端接口(模拟,假设已实现)
步骤 1:确保已封装 request.js
沿用之前的封装(核心是统一基础路径和拦截器):
javascript
运行
// src/utils/request.js
import axios from 'axios';
const request = axios.create({
baseURL: 'http://localhost:3000/api', // 后端接口基础路径
timeout: 5000
});
// 请求拦截器(添加 Token 等)
request.interceptors.request.use(
config => {
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
error => Promise.reject(error)
);
// 响应拦截器(统一处理响应和错误)
request.interceptors.response.use(
response => response.data, // 直接返回后端数据
error => {
alert('请求失败:' + (error.response?.data?.msg || error.message));
return Promise.reject(error);
}
);
export default request;
步骤 2:创建 API 层(统一管理接口)
在 api 目录下新建 user.js,专门存放用户相关接口,实现 “接口与组件分离”:
javascript
运行
// src/api/user.js
import request from '@/utils/request'; // 导入封装好的请求工具
/**
* 前端发送 User 对象,后端返回 User 列表
* @param {Object} user - 前端传入的 User 对象
* @param {string} user.name - 用户名
* @param {number} user.age - 年龄
* @returns {Promise<Array>} - 后端返回的 User 列表
*/
export const getUserList = (user) => {
// 发送 POST 请求,路径为 /user/list,参数为 user 对象
return request.post('/user/list', user);
};
API 层的好处:
- 所有接口集中管理,改地址或参数时不用找遍所有组件
- 清晰注释接口的参数和返回值,方便团队协作
步骤 3:页面组件调用 API(展示数据)
新建页面组件 UserList.vue,调用 API 层的方法,实现交互逻辑:
vue
<template>
<div class="user-list-page">
<h2>用户列表查询</h2>
<!-- 输入表单:收集 User 对象信息 -->
<div class="form">
<input
type="text"
v-model="searchUser.name"
placeholder="请输入用户名"
/>
<input
type="number"
v-model="searchUser.age"
placeholder="请输入年龄"
/>
<button @click="fetchUserList">查询用户列表</button>
</div>
<!-- 展示后端返回的 User 列表 -->
<div class="list" v-if="userList.length">
<h3>查询结果 ({{ userList.length }})</h3>
<ul>
<li v-for="(user, index) in userList" :key="index">
{{ user.id }} - {{ user.name }} ({{ user.age }}岁) - {{ user.address }}
</li>
</ul>
</div>
</div>
</template>
<script>
// 导入 API 层的接口方法
import { getUserList } from '@/api/user';
export default {
data() {
return {
// 前端要发送的 User 对象
searchUser: {
name: '',
age: null
},
// 存储后端返回的 User 列表
userList: []
};
},
methods: {
async fetchUserList() {
try {
// 调用 API 层方法,传入 User 对象
const result = await getUserList(this.searchUser);
// 假设后端返回格式:{ code: 200, data: [User, User...], msg: '成功' }
if (result.code === 200) {
this.userList = result.data; // 保存用户列表
} else {
alert(result.msg || '查询失败');
}
} catch (error) {
// 错误会被 request.js 拦截处理,这里可做额外逻辑
console.log('查询中断', error);
}
}
}
};
</script>
<style scoped>
.form {
margin: 20px 0;
display: flex;
gap: 10px;
}
input {
padding: 8px;
width: 200px;
}
button {
padding: 8px 16px;
background: #42b983;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.list ul {
list-style: none;
padding: 0;
}
.list li {
padding: 10px;
margin: 5px 0;
border: 1px solid #eee;
}
</style>
步骤 4:后端接口说明(模拟)
假设后端已实现 /api/user/list 接口,功能如下:
- 请求方式:POST
- 请求体:接收前端发送的 User 对象
json
{ "name": "张三", "age": 25 } - 返回数据:List<User> 列表(JSON 格式)
json
{ "code": 200, "msg": "查询成功", "data": [ { "id": 1, "name": "张三", "age": 25, "address": "北京" }, { "id": 2, "name": "张三", "age": 28, "address": "上海" } ] }
核心流程总结
-
数据流向:
页面输入 User 对象 → 调用api/user.js中的getUserList方法 →
request.js封装并发送请求 → 后端处理后返回 List<User> →
页面接收并展示列表 -
分层优势:
- 页面组件(UserList.vue):只关心 “怎么展示” 和 “什么时候调用接口”,不关心请求细节
- API 层(user.js):只关心 “调用哪个接口、传什么参数”,集中管理所有接口
- 工具层(request.js):只关心 “怎么发请求、怎么处理通用逻辑”,一次封装到处用
这种方式是企业级项目的标准实践,既清晰又易于维护。
【库存列表页面组件讲解:结构、逻辑与功能设计】
这个库存列表页面是典型的中后台管理系统数据列表页,遵循 “查询 - 展示 - 操作” 的核心流程,同时集成了弹窗式新增功能。下面从「组件结构」「核心逻辑」「功能模块」「设计亮点」四个维度展开讲解:
一、组件整体结构:三层嵌套的模块化设计
页面采用「顶部操作区 → 中间筛选区 → 底部列表 + 弹窗」的分层结构,每个区域职责明确,符合中后台页面的用户习惯:
| 区域层级 | 对应 DOM 结构 | 核心作用 | 技术依赖 |
|---|---|---|---|
| 1. 顶部操作区 | .page-header | 页面标题 + 核心操作(新增库存按钮) | Element Plus Button+Icon |
| 2. 中间筛选区 | .filter-card(el-card) | 多条件组合查询(商品名称 / 类别 / 状态) | Element Plus 表单组件 |
| 3. 底部内容区 | ① 表格区(el-table) ② 分页区(el-pagination) ③ 新增弹窗(el-dialog) | ① 数据展示 + 行操作 ② 分页控制 ③ 新增表单 | el-table/el-pagination/el-dialog |
二、核心逻辑:数据流转与状态管理
页面的核心是「数据查询 - 展示 - 新增」的闭环,通过 Vue 3 组合式 API 实现状态管理与逻辑解耦,关键逻辑如下:
1. 状态定义:区分 “静态配置” 与 “动态数据”
-
静态配置型状态:不会随用户操作变化,仅用于映射展示(如状态 / 类别标签的样式映射)
javascript
运行
// 状态标签映射:将后端返回的英文值(normal/warning)转为中文+对应颜色 const statusMap = { normal: { label: '正常', type: 'success' }, warning: { label: '预警', type: 'warning' }, out: { label: '缺货', type: 'danger' } }; // 类别标签映射:同理,实现“数据值→视觉展示”的解耦 const categoryMap = { electronic: { label: '电子设备', type: 'primary' }, office: { label: '办公用品', type: 'info' }, material: { label: '原材料', type: 'success' } }; -
动态数据型状态:随用户操作变化,需响应式管理
javascript
运行
const loading = ref(false); // 表格加载状态(控制loading动画) const inventoryList = ref([]); // 库存列表数据(表格渲染源) const searchForm = reactive({}); // 筛选表单数据(用户输入的查询条件) const pagination = reactive({}); // 分页数据(当前页/页大小/总条数) // 弹窗相关状态:控制弹窗显示/表单数据/验证规则 const addModalVisible = ref(false); const addForm = reactive({}); const addFormRules = reactive({});
2. 数据流转:从 “查询” 到 “展示” 的完整链路
(1)初始化加载:页面渲染时自动查数据
通过 onMounted 生命周期钩子,页面加载完成后立即调用查询接口,实现 “打开即见数据”:
javascript
运行
onMounted(() => {
fetchInventoryList(); // 核心查询函数
});
// 核心查询函数:整合筛选条件+分页,调用接口获取数据
const fetchInventoryList = async () => {
try {
loading.value = true; // 开启加载动画(优化用户体验)
// 1. 组装请求参数(筛选条件+分页参数)
const params = {
page: pagination.currentPage,
size: pagination.pageSize,
goodsName: searchForm.goodsName,
category: searchForm.category,
status: searchForm.status
};
// 2. 调用接口(实际项目中替换为真实请求,当前用模拟数据演示)
// const res = await request.get('/inventory/list', { params });
// inventoryList.value = res.data.list;
// pagination.total = res.data.total;
// 3. 模拟数据(开发阶段用于调试)
inventoryList.value = [/* 模拟数据 */];
pagination.total = 30;
} catch (error) {
ElMessage.error('获取库存列表失败'); // 错误提示(用户感知)
} finally {
loading.value = false; // 无论成功/失败,都关闭加载动画
}
};
(2)筛选查询:用户输入条件后刷新数据
- 点击 “搜索” 按钮:触发
handleSearch,重置页码为 1(避免筛选后页码超出范围),重新调用查询函数; - 点击 “重置” 按钮:触发
resetSearch,清空筛选表单 + 重置页码,重新查询 “全部数据”。
(3)分页操作:用户切换页码 / 页大小时更新数据
- 页大小变化(如 10 条→20 条):触发
handleSizeChange,更新pagination.pageSize,重新查询; - 当前页变化(如第 1 页→第 2 页):触发
handleCurrentChange,更新pagination.currentPage,重新查询。
3. 弹窗新增:“不跳转页面” 的局部操作
采用弹窗式新增而非跳转新页面,减少用户操作路径,核心逻辑分三步:
(1)打开弹窗:重置状态,避免数据残留
javascript
运行
const openAddModal = () => {
// 1. 重置表单数据(清空上次输入的内容)
addForm.goodsCode = '';
addForm.goodsName = '';
// ...其他表单字段重置
// 2. 重置表单验证状态(清除上次的错误提示)
if (addFormRef.value) addFormRef.value.clearValidate();
// 3. 显示弹窗
addModalVisible.value = true;
};
(2)表单验证:确保数据合法性
通过 addFormRules 定义验证规则(如商品编码必须 3-20 位字母数字、数量至少 1 件),提交前调用 validate 做校验:
javascript
运行
const submitAddForm = async () => {
// 1. 表单验证:未通过则不提交
const valid = await addFormRef.value.validate();
if (!valid) return;
try {
// 2. 调用新增接口(实际项目替换为真实请求)
// await request.post('/inventory/add', addForm);
ElMessage.success('入库成功'); // 成功提示
closeAddModal(); // 关闭弹窗
fetchInventoryList(); // 刷新列表(展示新增的数据)
} catch (error) {
ElMessage.error('入库失败'); // 失败提示
}
};
(3)关闭弹窗:清空状态,避免干扰下次操作
无论是点击 “取消” 还是关闭弹窗右上角叉号,都会触发 closeAddModal,清空表单数据和验证状态。
三、功能模块详解:每个模块的 “设计意图”
1. 表格展示模块:兼顾 “数据清晰” 与 “视觉区分”
- 列设计:包含 “序号、商品编码、名称、类别、库存数量、预警阈值、供应商、更新时间、状态、操作”10 列,覆盖库存管理的核心信息;
- 视觉区分:
- 库存数量:低于预警阈值时,文字标黄(
.warning-text样式),直观提醒 “库存不足”; - 状态 / 类别:用
el-tag标签 + 不同颜色(成功绿 / 预警黄 / 缺货红),快速区分数据状态;
- 库存数量:低于预警阈值时,文字标黄(
- 交互优化:表格行 hover 时背景变浅(
el-table tbody tr:hover),提升操作感知。
2. 行操作模块:编辑与删除的差异化处理
- 编辑:跳转到编辑页(
router.push(/inventory/edit?id=${row.id})),适合 “多字段修改” 的场景; - 删除:加确认弹窗(
ElConfirm),避免误操作,删除成功后刷新列表(确保数据实时同步)。
3. 弹窗新增模块:表单设计的 “用户友好” 细节
- 默认值:数量默认 1、预警阈值默认 10,减少用户输入成本;
- 输入限制:
- 数量 / 阈值:
type="number"+min="1",防止输入负数或 0; - 商品编码:正则校验(
/^[A-Za-z0-9]{3,20}$/),确保编码格式统一;
- 数量 / 阈值:
- 验证反馈:每个字段失去焦点时触发验证(
trigger: 'blur'),及时提示错误(如 “请输入商品名称”)。
四、设计亮点与可复用经验
- 状态与逻辑解耦:将 “静态配置”(如 statusMap)与 “动态数据”(如 inventoryList)分开定义,便于后期维护(如需新增状态,只需修改 statusMap);
- 用户体验优化:
- 加载动画:
loading状态控制表格加载,避免用户误以为 “页面卡住”; - 错误提示:所有接口操作都有
ElMessage反馈,让用户知道 “操作结果”; - 表单重置:弹窗打开 / 关闭时清空数据,避免上次输入干扰下次操作;
- 加载动画:
- 兼容性与扩展性:
- 模拟数据与真实接口分离:当前用模拟数据调试,后期替换为
request真实请求即可; - 分页支持:
page-sizes="[10,20,50]"提供多页大小选择,适配 “少数据快速看”“多数据批量查” 的场景。
- 模拟数据与真实接口分离:当前用模拟数据调试,后期替换为
【库存列表页面与后端接口交互全解析:参数、返回值与 IP 配置】
在库存列表页面与后端的交互中,核心是明确「请求参数格式」「返回参数结构」和「后端 IP 地址配置」三大模块,确保前后端数据流转顺畅。以下是分模块的详细分析:
一、核心交互接口:查询库存列表(GET /inventory/list)
这是列表页最基础的接口,用于根据筛选条件获取分页数据,是前后端交互的核心。
1. 请求参数:明确 “传什么、怎么传”
查询接口采用 GET 请求,参数通过「URL 查询字符串(Query String)」传递(如 ?page=1&size=10&goodsName=电脑),参数需覆盖「筛选条件」「分页控制」「排序规则」,具体如下:
| 参数分类 | 参数名 | 数据类型 | 是否必传 | 默认值 | 说明(前后端约定) | 前端来源 |
|---|---|---|---|---|---|---|
| 分页控制 | page | Integer | 否 | 1 | 当前页码(从 1 开始,不能为 0 或负数),用于控制分页跳转 | pagination.currentPage |
size | Integer | 否 | 10 | 每页条数(支持 10/20/50 等常见值,需与后端约定可选范围) | pagination.pageSize | |
| 筛选条件 | goodsName | String | 否 | 空字符串 | 商品名称模糊查询(如输入 “电脑”,后端匹配所有名称含 “电脑” 的库存) | searchForm.goodsName |
category | String | 否 | 空字符串 | 商品类别精确查询(值必须是后端约定的枚举:electronic/office/material) | searchForm.category | |
status | String | 否 | 空字符串 | 库存状态精确查询(值必须是后端约定的枚举:normal/warning/out) | searchForm.status | |
| 排序规则 | sortField | String | 否 | lastUpdateTime | 排序字段(后端支持的字段:lastUpdateTime(最近更新)、quantity(库存数量)等) | 前端固定 / 可配置 |
sortOrder | String | 否 | desc | 排序方向(asc 升序,desc 降序) | 前端固定 / 可配置 |
前端参数组装代码示例
在 fetchInventoryList 函数中,将前端状态(searchForm 筛选条件、pagination 分页)组装为请求参数:
javascript
运行
const fetchInventoryList = async () => {
try {
loading.value = true;
// 1. 组装请求参数(与上述表格一一对应)
const params = {
page: pagination.currentPage, // 分页:当前页
size: pagination.pageSize, // 分页:每页条数
goodsName: searchForm.goodsName, // 筛选:商品名称(模糊)
category: searchForm.category, // 筛选:商品类别(精确)
status: searchForm.status, // 筛选:库存状态(精确)
sortField: 'lastUpdateTime', // 排序:按最近更新时间
sortOrder: 'desc' // 排序:降序
};
// 2. 发送GET请求(params自动转为Query String)
const res = await request.get('/inventory/list', { params });
// 3. 处理返回数据(后续讲解)
inventoryList.value = res.data.list;
pagination.total = res.data.total;
} catch (error) {
ElMessage.error('获取库存列表失败');
} finally {
loading.value = false;
}
};
2. 返回参数:明确 “后端给什么、前端怎么用”
后端返回 JSON 格式数据,需包含「状态信息」和「核心业务数据」,结构需与前端渲染逻辑匹配,具体如下:
整体返回格式(标准结构)
json
{
"code": 200, // 状态码(核心:200=成功,非200=失败)
"message": "success", // 提示信息(成功=success,失败=具体原因,如“参数错误”)
"data": { // 核心业务数据(分页+列表)
"total": 120, // 总条数(前端用于分页组件显示“共120条”)
"list": [ // 当前页库存列表(与表格列一一对应)
{
"id": 1, // 库存唯一ID(用于编辑/删除)
"goodsCode": "ELEC001", // 商品编码(表格“商品编码”列)
"goodsName": "笔记本电脑", // 商品名称(表格“商品名称”列)
"category": "electronic", // 商品类别(前端通过categoryMap转中文)
"quantity": 25, // 库存数量(表格“库存数量”列,用于预警判断)
"warningThreshold": 10, // 预警阈值(表格“预警阈值”列)
"supplier": "科技设备供应商", // 供应商(表格“供应商”列)
"lastUpdateTime": "2024-05-15 14:30:22", // 最近更新时间(表格“最近更新”列)
"status": "normal" // 库存状态(前端通过statusMap转中文+颜色)
},
// ... 其他库存数据(数量=size参数值,如10条)
]
}
}
返回参数与前端的关联逻辑
code:前端判断请求是否成功(code === 200则处理数据,否则显示message错误提示);data.total:赋值给pagination.total,分页组件自动计算总页数(如total=120、size=10→ 总页数 12);data.list:赋值给inventoryList.value,作为表格数据源(el-table :data="inventoryList");category/status:后端返回英文枚举(如electronic),前端通过预定义的categoryMap/statusMap映射为 “中文 + 标签颜色”(如electronic→ “电子设备”+ 蓝色标签),实现前后端解耦。
二、辅助接口:新增 / 删除库存(参数补充)
除了查询接口,库存列表还涉及 “新增” 和 “删除” 两个辅助接口,参数与返回值如下:
1. 新增库存(POST /inventory/add)
-
请求参数:通过「请求体(Body)」传递 JSON 格式数据(与弹窗表单
addForm结构一致),参数见下表:参数名 数据类型 是否必传 说明(前后端校验) goodsCodeString 是 商品编码(3-20 位字母数字,后端需校验唯一性) goodsNameString 是 商品名称(非空,长度≤50 字符) categoryString 是 商品类别(必须是 electronic/office/material之一)quantityInteger 是 入库数量(≥1,整数) warningThresholdInteger 是 预警阈值(≥1,整数,建议小于 quantity)supplierString 是 供应商(非空,长度≤50 字符) -
返回参数:简洁的状态提示(无需业务数据):
json
{ "code": 200, "message": "入库成功", "data": null }
2. 删除库存(DELETE /inventory/{id})
- 请求参数:通过「URL 路径(Path)」传递
id(库存唯一标识),如DELETE /inventory/1(删除 ID=1 的库存); - 返回参数:简洁的状态提示:
json
{ "code": 200, "message": "删除成功", "data": null }
三、后端 IP 地址配置:明确 “请求发往哪里”
前端发送请求时,需要指定后端服务器的 IP 地址(或域名),通常通过「环境变量」配置(区分开发 / 生产环境),避免硬编码(硬编码会导致切换环境时需修改大量代码)。
1. 配置位置:环境变量文件
在 Vue 项目的 src 同级目录下,创建 环境变量文件,不同环境(开发 / 生产)配置不同的 IP:
| 环境 | 文件名 | 配置内容(示例) |
|---|---|---|
| 开发环境 | .env.development | VUE_APP_BASE_API = 'http://192.168.1.100:8080/api'(本地 / 测试服务器 IP) |
| 生产环境 | .env.production | VUE_APP_BASE_API = 'http://www.xxx.com/api'(线上服务器域名 / IP) |
- 说明:
VUE_APP_是 Vue 规定的环境变量前缀,只有带这个前缀的变量才能在前端代码中访问; - 示例 IP:
192.168.1.100是后端服务器的局域网 IP,8080是后端服务端口,/api是后端接口的统一前缀(如http://192.168.1.100:8080/api/inventory/list)。
2. 封装请求工具:统一使用 IP 配置
创建 src/utils/request.js(Axios 封装工具),将环境变量中的 IP 作为基础 URL,所有请求自动拼接:
javascript
运行
// src/utils/request.js
import axios from 'axios';
// 创建Axios实例,基础URL=环境变量中的IP+接口前缀
const request = axios.create({
baseURL: import.meta.env.VUE_APP_BASE_API, // 关键:读取环境变量中的IP配置
timeout: 5000 // 请求超时时间(5秒)
});
// 请求拦截器(可选:如添加Token、设置请求头)
request.interceptors.request.use(
(config) => {
// 示例:添加登录Token(如果后端需要身份验证)
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
// 响应拦截器(可选:统一处理错误)
request.interceptors.response.use(
(response) => {
// 直接返回响应体中的data(简化前端处理)
return response.data;
},
(error) => {
// 统一错误提示(如网络错误、服务器错误)
ElMessage.error(error.message || '请求失败,请重试');
return Promise.reject(error);
}
);
export default request;
3. 实际请求效果
当前端调用 request.get('/inventory/list', { params }) 时:
- 开发环境:实际请求 URL =
http://192.168.1.100:8080/api+/inventory/list→http://192.168.1.100:8080/api/inventory/list; - 生产环境:实际请求 URL =
http://www.xxx.com/api+/inventory/list→http://www.xxx.com/api/inventory/list。
无需手动拼接 IP,切换环境时只需修改对应的 .env 文件即可。
1. 开发环境文件(.env.development)
env
# 开发环境:连接测试服务器的IP(比如局域网IP)
VUE_APP_BASE_API = "http://192.168.1.100:8080/api"
- 说明:
192.168.1.100是测试服务器的局域网 IP,8080是后端服务端口,/api是后端接口的统一前缀(如果后端没有前缀,就写成http://192.168.1.100:8080)。
2. 生产环境文件(.env.production)
env
# 生产环境:连接正式服务器的IP或域名
VUE_APP_BASE_API = "http://www.yourserver.com/api"
- 说明:
www.yourserver.com是正式服务器的域名(也可以写公网 IP,比如http://203.0.113.5:8080/api)。
步骤 3:无需修改 request.js,自动生效
你原有 request.js 里的这行代码已经写好了:
javascript
运行
const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API, // 自动读取当前环境的IP
timeout: 5000,
headers: { 'Content-Type': 'application/json;charset=utf-8' }
});
- 当你执行
npm run serve(开发模式)时,Vue 会自动读取.env.development里的 IP; - 当你执行
npm run build(打包生产版本)时,Vue 会自动读取.env.production里的 IP; - 完全不需要改
request.js,也和token认证无关(如果暂时不需要token,把request.js里关于token的两行代码注释掉即可,不影响 IP 配置)。
1323

被折叠的 条评论
为什么被折叠?



