快速上手 vue+element

【项目搭建】

一、第一步:用 vue create 重建 Vue 项目

首先通过 Vue CLI 初始化项目,这是基础框架:

  1. 打开终端,执行创建命令(替换 your-project-name 为你的项目名,比如 inventory-system):

    bash

    vue create your-project-name
    
  2. 选择项目配置(按需求选,新手推荐 Manually select features 手动勾选核心功能):
    • 勾选 Babel(必选,转译 ES6+ 语法)、CSS Pre-processors(可选,如需 Less/Sass)、Linter / Formatter(可选,代码规范检查);
    • 不需要勾选 Router(因为后续手动装)、Vuex/Pinia(如果项目需要状态管理再补装,暂时可忽略)。
  3. 后续按提示选择:
    • CSS 预处理器:推荐 Less(Element Plus 默认支持);
    • 代码规范:推荐 ESLint + Standard config(通用且严格);
    • 配置文件存放:选 In dedicated config files(单独文件管理,更清晰)。
  4. 等待依赖安装完成,进入项目目录:

    bash

    cd your-project-name
    

二、第二步:手动安装核心依赖(按顺序来)

你提到的 routerElement 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 中引入即可,配置比较简单,按需使用即可。

【文件功能目录】

一、核心业务目录(日常开发的主要工作区)

优先级目录 / 文件核心作用关键文件格式开发频率
1src/views/页面级组件(对应路由的完整页面,如首页、详情页).vue极高
2src/components/可复用组件(通用组件如 Button,业务组件如 OrderCard,被 views 引用).vue极高
3src/router/路由配置(页面跳转规则、嵌套路由、路由守卫).js/.ts
4src/store/全局状态管理(Pinia/Vuex,存储跨组件共享数据如用户信息、购物车).js/.ts
5src/api/接口请求封装(统一管理后端 API 调用,避免重复写 axios 逻辑).js/.ts

二、辅助开发目录(支撑业务实现的工具 / 资源)

优先级目录 / 文件核心作用关键文件格式开发频率
6src/utils/工具函数(时间格式化、数据校验、权限判断等通用逻辑).js/.ts
7src/hooks/自定义钩子(Vue3 特性,封装复用逻辑如防抖、表单验证、接口请求).js/.ts
8src/assets/静态资源(图片、字体、图标等,会被 webpack 处理).png/.svg/.woff
9src/styles/全局样式(主题变量、重置样式、公共 CSS 类).css/.scss/.less

三、项目配置与输出目录(环境与部署相关)

优先级目录 / 文件核心作用关键文件格式开发频率
10public/纯静态资源(不被编译,直接复制到输出目录,如 index.html、favicon.ico).html/.ico
11src/main.js项目入口文件(创建 Vue 实例、挂载根组件、引入路由 /store).js/.ts极低
12dist/构建输出目录(执行 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 个关键特点:

  1. 高内聚:一个组件内,HTML(模板)、CSS(样式)、JS(逻辑)是 “打包好的”,比如一个「按钮组件」,它的外观(红色、圆角)和点击逻辑(弹窗提示)都封装在自己内部,不依赖外部代码。
  2. 低耦合:组件之间相互独立,修改一个组件的逻辑 / 样式,不会影响其他组件。比如改了「列表项组件」的字体颜色,不会导致「搜索框组件」出问题。
  3. 可复用:写一次组件,能在多个页面甚至多个项目中重复使用。比如一个「分页组件」,首页、列表页、详情页需要分页时,直接 “拿来用” 即可,不用重复写分页逻辑。
  4. 可组合:小组件能拼合成大小件,大小件能拼合成页面。比如:「商品卡片组件」(小)→ 「商品列表组件」(中)→ 「商品首页」(页面)。

二、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 种方式协作:

  1. 父传子(Props):父组件给子组件传数据。
    比如父组件要让子组件显示 “购买按钮”,就给子组件的 buttonText 传值:

    vue

    <!-- 父组件中使用子组件 -->
    <template>
      <MyButton buttonText="立即购买" /> <!-- 传值给子组件的 props -->
    </template>
    
  2. 子传父(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>
    
  3. 插槽(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

核心总结

  1. 父传子

    • 子组件用 props 声明接收的数据
    • 父组件在使用子组件时,用 :属性名="数据" 传值
  2. 子传父

    • 子组件用 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,浏览器原生支持。
  • 痛点(致命问题)
    1. 页面刷新:每次点击都会刷新整个页面,屏幕会闪一下,体验差。
    2. 资源重复加载:新页面需要重新加载 CSS、JS、图片等资源,即使和旧页面有重复(比如导航栏、页脚),也会重新加载。
    3. 状态丢失:旧页面的临时数据(比如表单输入的内容、滚动位置)会完全丢失。

第二步:优化刷新问题 ——JS 历史记录 API(无刷新跳转)

为了解决 “页面刷新” 的痛点,浏览器提供了 History APIpushState/replaceState),允许通过 JS 修改地址栏 URL,但不发送服务器请求、不刷新页面

核心逻辑
  1. 用 JS 监听点击事件,阻止 <a> 标签的默认跳转行为(避免刷新)。
  2. 用 history.pushState() 修改地址栏 URL,同时把新 URL 存入浏览器历史记录(支持后退按钮)。
  3. 根据新 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>
优点 & 新痛点
  • 优点:实现了 “无刷新跳转”,体验大幅提升,资源不用重复加载,页面状态也能保留。
  • 新痛点(工程化问题)
    1. 手动控制 DOM 太麻烦:如果页面多(比如 10 个页面),要写大量 if-else 控制显示 / 隐藏,代码臃肿。
    2. URL 与内容的映射混乱:URL 变了要手动对应到具体的页面内容,没有统一的 “规则” 管理。
    3. 不支持组件化:在 Vue/React 这类组件化框架中,我们需要的是 “URL 对应组件”,而不是 “URL 对应 DOM 显示 / 隐藏”。

第三步:框架适配 ——Vue Router 的诞生(URL 对应组件)

Vue 是组件化框架,所有页面都是由组件构成的。为了解决 “URL 与组件映射” 的痛点,Vue 官方推出了 Vue Router—— 它本质是对 “History API” 的封装,核心是实现 “URL 路径 → 对应组件” 的自动映射

核心逻辑
  1. 定义路由规则:告诉 Vue Router “哪个 URL 对应哪个组件”。
  2. 挂载路由容器:在页面中留一个 “坑”(<router-view>),让匹配的组件自动渲染到这里。
  3. 用路由链接跳转:用 <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>
优点:解决了之前的所有痛点
  1. 无刷新跳转:基于 History API,体验流畅。
  2. 自动映射组件:URL 对应组件的规则统一管理,不用手动写 if-else 控制 DOM。
  3. 支持组件化:完美适配 Vue 的组件化开发,每个页面就是一个组件。
  4. 额外能力:还支持嵌套路由(比如 /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 个核心文件,形成清晰的分层结构:

  1. utils/request.js:封装 axios(基础请求工具)
  2. api/user.js:统一管理用户相关接口(API 层)
  3. views/UserList.vue:页面组件(调用 API 并展示数据)
  4. 后端接口(模拟,假设已实现)

步骤 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": "上海" }
      ]
    }
    

核心流程总结

  1. 数据流向
    页面输入 User 对象 → 调用 api/user.js 中的 getUserList 方法 →
    request.js 封装并发送请求 → 后端处理后返回 List<User> →
    页面接收并展示列表

  2. 分层优势

    • 页面组件(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'),及时提示错误(如 “请输入商品名称”)。

四、设计亮点与可复用经验

  1. 状态与逻辑解耦:将 “静态配置”(如 statusMap)与 “动态数据”(如 inventoryList)分开定义,便于后期维护(如需新增状态,只需修改 statusMap);
  2. 用户体验优化
    • 加载动画:loading 状态控制表格加载,避免用户误以为 “页面卡住”;
    • 错误提示:所有接口操作都有 ElMessage 反馈,让用户知道 “操作结果”;
    • 表单重置:弹窗打开 / 关闭时清空数据,避免上次输入干扰下次操作;
  3. 兼容性与扩展性
    • 模拟数据与真实接口分离:当前用模拟数据调试,后期替换为 request 真实请求即可;
    • 分页支持:page-sizes="[10,20,50]" 提供多页大小选择,适配 “少数据快速看”“多数据批量查” 的场景。

【库存列表页面与后端接口交互全解析:参数、返回值与 IP 配置】

在库存列表页面与后端的交互中,核心是明确「请求参数格式」「返回参数结构」和「后端 IP 地址配置」三大模块,确保前后端数据流转顺畅。以下是分模块的详细分析:

一、核心交互接口:查询库存列表(GET /inventory/list)

这是列表页最基础的接口,用于根据筛选条件获取分页数据,是前后端交互的核心。

1. 请求参数:明确 “传什么、怎么传”

查询接口采用 GET 请求,参数通过「URL 查询字符串(Query String)」传递(如 ?page=1&size=10&goodsName=电脑),参数需覆盖「筛选条件」「分页控制」「排序规则」,具体如下:

参数分类参数名数据类型是否必传默认值说明(前后端约定)前端来源
分页控制pageInteger1当前页码(从 1 开始,不能为 0 或负数),用于控制分页跳转pagination.currentPage
sizeInteger10每页条数(支持 10/20/50 等常见值,需与后端约定可选范围)pagination.pageSize
筛选条件goodsNameString空字符串商品名称模糊查询(如输入 “电脑”,后端匹配所有名称含 “电脑” 的库存)searchForm.goodsName
categoryString空字符串商品类别精确查询(值必须是后端约定的枚举:electronic/office/materialsearchForm.category
statusString空字符串库存状态精确查询(值必须是后端约定的枚举:normal/warning/outsearchForm.status
排序规则sortFieldStringlastUpdateTime排序字段(后端支持的字段:lastUpdateTime(最近更新)、quantity(库存数量)等)前端固定 / 可配置
sortOrderStringdesc排序方向(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=120size=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.developmentVUE_APP_BASE_API = 'http://192.168.1.100:8080/api'(本地 / 测试服务器 IP)
生产环境.env.productionVUE_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 配置)。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值