Skip to content

一、vue基础回顾+进阶

更新: 2025/4/9 字数: 0 字 时长: 0 分钟

课程内容

  • VUE 基础回顾
  • 路由 Vue-Router
  • 状态管理 vuex
  • TypeScript

1. VUE 基础回顾

1.1 基于脚手架创建前端工程

1.1.1 环境要求

要想基于脚手架创建前端工程,需要具备如下环境要求:

  • node.js 前端项目的运行环境
  • npm JavaScript的包管理工具
  • Vue CLI 基于Vue进行快速开发的完整系统,实现交互式的项目脚手架

安装完node.js后,可以通过命令行来查看版本号,如下:

image-20230925094426687

安装 Vue CLI,命令如下:

image-20230925094630147

1.1.2 操作过程

使用 Vue CLI 创建前端工程的方式:

  • ​ 方式一:vue create 项目名称
  • ​ 方式二:vue ui

重点介绍使用 vue ui 命令创建前端工程的过程:

第一步:在命令行输入命令 vue ui,在浏览器ui界面中选择前端工程存放的位置

image-20230925100950987

第二步:点击“在此创建新项目”按钮,跳转到创建新项目设置页面

image-20230925101106692

第三步:填写项目名称、选择包管理器为npm,点击“下一步”按钮

image-20230925101202363

第四步:选择 Default(Vue 2),点击"创建项目"按钮,完成项目的创建

image-20230925101317873

1.1.3 工程结构

工程目录结构:

image-20230925103418213

重点文件或目录介绍

  • node_modules:当前项目依赖的js包
  • assets:静态资源存放目录
  • components:公共组件存放目录
  • App.vue:项目的主组件,页面的入口文件
  • main.js:整个项目的入口文件
  • package.json:项目的配置信息、依赖包管理
  • vue.config.js:vue-cli配置文件

1.1.4 启动前端服务

使用VS Code打开创建的前端工程,启动前端工程:

image-20230925103813210

访问前端工程:

image-20230925103913672

注:要停止前端服务,可以在命令行终端使用 ctrl + C

前端项目启动后,服务端口默认为8080,很容易和后端tomcat端口号冲突。如何修改前端服务的端口号?

可以在vue.config.js中配置前端服务端口号:

javascript
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
  transpileDependencies: true,
  devServer: {
    port: 7070  //指定前端服务端口号
  }
})

1.2 vue基本使用方式

本章节从如下几个方面进行vue回顾:

  • vue 组件
  • 文本插值
  • 属性绑定
  • 事件绑定
  • 双向绑定
  • 条件渲染
  • axios

1.2.1 vue 组件

Vue 的组件文件以 .vue 结尾,每个组件由三部分组成:

  • 结构 <template>
  • 样式 <style>
  • 逻辑 <script>

image-20230925111404674

1.2.2 文本插值

作用:用来绑定 data 方法返回的对象属性

用法:

示例:

image-20230925111739838

1.2.3 属性绑定

作用:为标签的属性绑定 data 方法中返回的属性

用法:v-bind:xxx,简写为 :xxx

示例:

image-20230925112435816

1.2.4 事件绑定

作用:为元素绑定对应的事件

用法:v-on:xxx,简写为 @xxx

示例:

image-20230925112514956

1.2.5 双向绑定

作用:表单输入项和 data 方法中的属性进行绑定,任意一方改变都会同步给另一方

用法:v-model

示例:

image-20230925112600375

1.2.6 条件渲染

作用:根据表达式的值来动态渲染页面元素

用法:v-if、v-else、v-else-if

示例:

image-20230925112635467

1.2.7 axios

Axios 是一个基于 promise 的 网络请求库,作用于浏览器和 node.js 中。使用Axios可以在前端项目中发送各种方式的HTTP请求。

安装命令npm install axios

bash
npm install axios

导入:import axios from 'axios'

axios 的 API 列表:

image-20230925112806943

参数说明:

  • url:请求路径
  • data:请求体数据,最常见的是JSON格式数据
  • config:配置对象,可以设置查询参数、请求头信息

注:在使用axios时,经常会遇到跨域问题。为了解决跨域问题,可以在 vue.config.js 文件中配置代理

javascript
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
  transpileDependencies: true,
  devServer: {
    port: 7070,
    proxy: {
      '/api': {
        target: 'http://localhost:8080',
        pathRewrite: {
          '^/api': ''
        }
      }
    }
  }
})

axios的post请求示例:

javascript
axios.post('/api/admin/employee/login',{
      username:'admin',
      password: '123456'
    }).then(res => {
      console.log(res.data)
    }).catch(error => {
      console.log(error.response)
    })

axios的get请求示例:

javascript
axios.get('/api/admin/shop/status',{
        headers: {
          token: ‘xxx.yyy.zzz’
        }
      })

axios提供的统一使用方式示例一(可以发送各种方式的请求):

image-20230925113501390

axios提供的统一使用方式示例二(可以发送各种方式的请求):

javascript
axios({
      url: '/api/admin/employee/login',
      method:'post',
      data: {
        username:'admin',
        password: '123456'
      }
    }).then((res) => {
      console.log(res.data.data.token)
      axios({
        url: '/api/admin/shop/status',
        method: 'get',
        params: {id: 100},
        headers: {
          token: res.data.data.token
        }
      })
    }).catch((error) => {
      console.log(error)
    })

2. 路由 Vue-Router

2.0 NPM安装Router路由

bash
npm install vue-router

如果在一个模块化工程中使用它,必须要通过 Vue.use() 明确地安装路由功能:

js
import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter)

如果使用全局的 script 标签,则无须如此 (手动安装)。

2.1 Vue-Router 介绍

vue 属于单页面应用,所谓路由,就是根据浏览器路径不同,用不同的视图组件替换这个页面内容。

image-20230925142650279

image-20230925142705351

如上图所示:不同的访问路径,对应不同的页面展示。

在vue应用中使用路由功能,需要安装Vue-Router:

image-20230925142957238

注:创建完带有路由功能的前端项目后,在工程中会生成一个路由文件,如下所示:

image-20230925144015768

关于路由的配置,主要就是在这个路由文件中完成的。

为了能够使用路由功能,在前端项目的入口文件main.js中,创建Vue实例时需要指定路由对象:

image-20230925144400285

2.2 路由配置

首先了解一下路由组成

  • VueRouter:路由器,根据路由请求在路由视图中动态渲染对应的视图组件
  • <router-link>:路由链接组件,浏览器会解析成<a>
  • <router-view>:路由视图组件,用来展示与路由路径匹配的视图组件

image-20230925143537869

具体配置方式:

  1. 在路由文件中配置路由路径和视图的对应关系:
javascript
import Vue from 'vue'
import VueRouter from 'vue-router'
import HomeView from '../views/HomeView.vue'

Vue.use(VueRouter)

//维护路由表,某个路由路径对应哪个视图组件
const routes = [
  {
    path: '/',
    name: 'home',
    component: HomeView
  },
  {
    path: '/about',
    name: 'about',
    component: () => import(/* webpackChunkName: "about" */ '../views/AboutView.vue')
  }
  ,
  {
    path: '/404',
    component: () => import('../views/404View.vue')
  },
  {
    path: '*',
    redirect: '/404'
  }
]

const router = new VueRouter({
  routes
})

export default router
  1. 在视图组件中配置 router-link标签,用于生成超链接
html
`<router-link to="/">`Home`</router-link>` |
`<router-link to="/about">`About`</router-link>` |
`<router-link to="/test">`Test`</router-link>` |
  1. 在视图组件汇总配置router-view标签
html
`<!--视图组件展示的位置-->`
`<router-view/>`

要实现路由跳转,可以通过标签式和编程式两种:

  • 标签式<router-link to="/about">About</router-link>
  • 编程式:this.$router.push('/about')
    • this.$router用来获取路由对象
    • push方法是根据url进行跳转

**问题思考:**如果用户访问的路由地址不存在,该如何处理?

可以通过配置一个404视图组件,当访问的路由地址不存在时,则重定向到此视图组件,具体配置如下:

javascript
  {
    path: '/404',
    component: () => import('../views/404View.vue')
  },
  {
    path: '*',
    redirect: '/404' //重定向
  }
vue
<template>
  <div id="app">
    <nav>
      <router-link to="/">Home</router-link> |
      <router-link to="/about">About</router-link> |
      <router-link to="/test">Test</router-link> |
      <input type="button" value="编程式路由跳转" @click="jump"/>
    </nav>

    <!--视图组件展示的位置-->
    <router-view/>

  </div>
</template>


<script>
export default {
  methods:{
    jump(){
      //使用编程式路由跳转
      this.$router.push('/about')
    }
  }
}
</script>


<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
}

nav {
  padding: 30px;
}

nav a {
  font-weight: bold;
  color: #2c3e50;
}

nav a.router-link-exact-active {
  color: #42b983;
}
</style>

2.3 嵌套路由

嵌套路由:组件内要切换内容,就需要用到嵌套路由(子路由),效果如下:

在App.vue视图组件中有<router-view>标签,其他视图组件可以展示在此

image-20230925153930006

ContainerView.vue组件可以展示在App.vue视图组件的<router-view>位置

image-20230925153854881

ContainerView.vue组件进行了区域划分(分为上、左、右),在右边编写了<router-view>标签,点击左侧菜单时,可以将对应的子视图组件展示在此

image-20230925154346635

实现步骤:

第一步:安装并导入 elementui,实现页面布局(Container 布局容器)---ContainerView.vue

mian.js文件中导入ElementUI

image-20240827224336176

npm i element-ui -s
js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';

Vue.config.productionTip = false

//全局使用ElementUI
Vue.use(ElementUI);

new Vue({
  router,
  render: h => h(App)
}).$mount('#app')
html
`<template>`
  `<el-container>`
    `<el-header>`Header`</el-header>`
    `<el-container>`
        `<el-aside width="200px">`
        `</el-aside>`
        `<el-main>`
        `</el-main>`
    `</el-container>`
  `</el-container>`
`</template>`

`<script>`
export default {

}
`</script>`

`<style>`
.el-header, .el-footer {
    background-color: #B3C0D1;
    color: #333;
    text-align: center;
    line-height: 60px;
  }
  
  .el-aside {
    background-color: #D3DCE6;
    color: #333;
    text-align: center;
    line-height: 200px;
  }
  
  .el-main {
    background-color: #E9EEF3;
    color: #333;
    text-align: center;
    line-height: 160px;
  }
  
  body > .el-container {
    margin-bottom: 40px;
  }
  
  .el-container:nth-child(5) .el-aside,
  .el-container:nth-child(6) .el-aside {
    line-height: 260px;
  }
  
  .el-container:nth-child(7) .el-aside {
    line-height: 320px;
  }
`</style>`

第二步:提供子视图组件,用于效果展示 ---P1View.vue、P2View.vue、P3View.vue

html
`<template>`
  `<div>`
    这是P1 View
  `</div>`
`</template>`

`<script>`
export default {

}
`</script>`

`<style>`
.el-header, .el-footer {
    background-color: #B3C0D1;
    color: #333;
    text-align: center;
    line-height: 60px;
  }
  
  .el-aside {
    background-color: #D3DCE6;
    color: #333;
    text-align: center;
    line-height: 200px;
  }
  
  .el-main {
    background-color: #E9EEF3;
    color: #333;
    text-align: center;
    line-height: 160px;
  }
  
  body > .el-container {
    margin-bottom: 40px;
  }
  
  .el-container:nth-child(5) .el-aside,
  .el-container:nth-child(6) .el-aside {
    line-height: 260px;
  }
  
  .el-container:nth-child(7) .el-aside {
    line-height: 320px;
  }
`</style>`

第三步:在 src/router/index.js 中配置路由映射规则(嵌套路由配置)

javascript
   {
    path: '/c',
    component: () => import('../views/container/ContainerView.vue'),
    //嵌套路由(子路由),对应的组件会展示在当前组件内部
    children: [//通过children属性指定子路由相关信息(path、component)
      {
        path: '/c/p1',
        component: () => import('../views/container/P1View.vue')
      },
      {
        path: '/c/p2',
        component: () => import('../views/container/P2View.vue')
      },
      {
        path: '/c/p3',
        component: () => import('../views/container/P3View.vue')
      }
    ]
  }

第四步:在ContainerView.vue 布局容器视图中添加<router-view>,实现子视图组件展示

html
`<el-main>`
    `<router-view/>`
`</el-main>`

第五步:在ContainerView.vue 布局容器视图中添加<router-link>,实现路由请求

html
`<el-aside width="200px">`
    `<router-link to="/c/p1">`P1`</router-link>``<br>`
    `<router-link to="/c/p2">`P2`</router-link>``<br>`
    `<router-link to="/c/p3">`P3`</router-link>``<br>`
`</el-aside>`

注意:子路由变化,切换的是【ContainerView 组件】中 <router-view></router-view> 部分的内容

问题思考:

1.对于前面的案例,如果用户访问的路由是 /c,会有什么效果呢?

image-20230925160657497

2.如何实现在访问 /c 时,默认就展示某个子视图组件呢?

配置重定向,当访问/c时,直接重定向到/c/p1即可,如下配置:

image-20230925160730746

3. 状态管理 vuex

3.1 vuex 介绍

  • vuex 是一个专为 Vue.js 应用程序开发的状态管理库

  • vuex 可以在多个组件之间共享数据,并且共享的数据是响应式的,即数据的变更能及时渲染到模板

  • vuex 采用集中式存储管理所有组件的状态

每一个 Vuex 应用的核心就是 store(仓库)。“store”基本上就是一个容器,它包含着你的应用中大部分的状态 (state)。Vuex 和单纯的全局对象有以下两点不同:

  1. Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新
  2. 你不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地**提交 (commit) mutation**。这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。

安装vuexnpm install vuex@next --save

bash
npm install vuex@next --save

vuex中的几个核心概念:

  • state:状态对象,集中定义各个组件共享的数据

  • mutations:类似于一个事件,用于修改共享数据,要求必须是同步函数

  • actions:类似于mutation,可以包含异步操作(axios异步请求),通过调用mutation来改变共享数据

3.2 使用方式

本章节通过一个案例来学习vuex的使用方式,具体操作步骤如下:

第一步:创建带有vuex功能的前端项目

image-20230926094537147

注:在创建的前端工程中,可以发现自动创建了vuex相关的文件(src/store/index.js),并且在main.js中创建Vue实例时,需要将store对象传入,代码如下:

javascript
import Vue from 'vue'
import App from './App.vue'
import store from './store'

Vue.config.productionTip = false

new Vue({
  store,//使用vuex功能
  render: h => h(App)
}).$mount('#app')

第二步:在src/store/index.js文件中集中定义和管理共享数据

javascript
import Vue from 'vue'
import Vuex from 'vuex'
import axios from 'axios'

Vue.use(Vuex)

//集中管理多个组件共享的数据
export default new Vuex.Store({
  //集中定义共享数据
  state: {
    name: '未登录游客'
  },
  getters: {
  },
  //通过当前属性中定义的函数修改共享数据,必须都是同步操作
  mutations: {
  },
  //通过actions调用mutation,在actions中可以进行异步操作
  actions: {
  },
  modules: {
  }
})

第三步:在视图组件中展示共享数据

html
`<template>`
  `<div class="hello">`
    `<h1>`欢迎你,{{$store.state.name}}`</h1>`
  `</div>`
`</template>`

注:$store.state为固定写法,用于访问共享数据

第四步:在mutations中定义函数,用于修改共享数据

javascript
  //通过当前属性中定义的函数修改共享数据,必须都是同步操作
  mutations: {
    setName(state,newName) {
      state.name = newName
    }
  },

第五步:在视图组件中调用 mutations 中定义的函数

image-20230926102311897

注:mutations中定义的函数不能直接调用,必须通过状态对象的 commit 方法来调用

第六步:如果在修改共享数据的过程中有异步操作,则需要将异步操作的代码编写在actions的函数

javascript
  //通过actions调用mutation,在actions中可以进行异步操作
  actions: {
    setNameByAxios(context){
      axios({ //异步请求
        url: '/api/admin/employee/login',
        method: 'post',
        data: {
          username: 'admin',
          password: '123456'
        }
      }).then(res => {
        if(res.data.code == 1){
          //异步请求后,需要修改共享数据
          //在actions中调用mutation中定义的setName函数
          context.commit('setName',res.data.data.name)
        }
      })
    }
  },

注:在actions中定义的函数可以声明context参数,通过此参数可以调用mutations中定义的函数

第七步:在视图组件中调用actions中定义的函数

vue
<template>
  <div id="app">

    <h1>欢迎你,{{ $store.state.name }} -- App.vue</h1>

    <input type="button" value="通过mutations修改共享数据" @click="handleUpdate" />
    <input type="button" value="调用actions中的函数修改共享数据" @click="handleCallAction" />

    <HelloWorld msg="Welcome to Your Vue.js App" />
  </div>
</template>

<script>
import HelloWorld from './components/HelloWorld.vue'

export default {
  name: 'App',
  components: {
    HelloWorld
  },
  methods: {
    //mutations中定义的函数不能直接调用,必须通过commit这种方式间接调用
    //setName为mutations中定义的函数名称,joker为传递的参数
    handleUpdate() {
      this.$store.commit('setName', 'joker')
    },
    handleCallAction() {
      //调用actions中定义的函数,setNameByAxios为函数名称
      this.$store.dispatch('setNameByAxios')
    }
  }
}
</script>

image-20230926103114922

注:在actions中定义的函数不能直接调用,必须通过 this.$store.dispatch('函数名称') 这种方式调用

4. TypeScript

4.1 TypeScript 介绍

  • TypeScript(简称:TS) 是微软推出的开源语言
  • TypeScript 是 JavaScript 的超集(JS 有的 TS 都有)

image-20230926112525531

  • TypeScript = Type + JavaScript(在 JS 基础上增加了类型支持)
  • TypeScript 文件扩展名为 ts
  • TypeScript 可编译成标准的 JavaScript,并且在编译时进行类型检查

image-20230926112649750

在前端项目中使用TS,需要进行安装,命令为:npm install -g typescript

查看TS版本:

image-20230926112830756

TS初体验:

  1. 创建 hello.ts 文件,内容如下:
typescript
//定义一个函数 hello,并且指定参数类型为string
function hello(msg:string) {
      console.log(msg)
}

//调用上面的函数,传递非string类型的参数
hello(123)
  1. 使用 tsc 命令编译 hello.ts 文件

image-20230926142314810

可以看到编译报错,提示参数类型不匹配。这说明在编译时TS会进行类型检查。需要注意的是在编译为JS文件后,类型会被擦除

思考:TS 为什么要增加类型支持 ?

  • TS 属于静态类型编程语言,JS 属于动态类型编程语言
  • 静态类型在编译期做类型检查,动态类型在执行期做类型检查
  • 对于 JS 来说,需要等到代码执行的时候才能发现错误(晚)
  • 对于 TS 来说,在代码编译的时候就可以发现错误(早)
  • 配合 VSCode 开发工具,TS 可以提前到在编写代码的同时就发现代码中的错误,减少找 Bug、改 Bug 的时间

在前端项目中使用TS,需要创建基于TS的前端工程:

image-20230926145235469

image-20230926145349976

4.2 TypeScript 常用类型

TS中的常用类型如下:

类型备注
字符串类型string
数字类型number
布尔类型boolean
数组类型number[],string[], boolean[] 依此类推
任意类型any相当于又回到了没有类型的时代
复杂类型type 与 interface
函数类型() => void对函数的参数和返回值进行说明
字面量类型"a"|"b"|"c"限制变量或参数的取值
class 类class Animal

4.2.1 类型标注的位置

基于TS进行前端开发时,类型标注的位置有如下3个:

  • 标注变量
  • 标注参数
  • 标注返回值

image-20230926145517782

4.2.2 字符串、数字、布尔类型

字符串、数字、布尔类型是前端开发中常用的类型

image-20230926145634149

4.2.3 字面量类型

字面量类型用于限定数据的取值范围,类似于java中的枚举

image-20230926145813932

4.2.4 interface 类型

interface 类型是TS中的复杂类型,它让 TypeScript 具备了 JavaScript 所缺少的、描述较为复杂数据结构的能力。

image-20230926150816090

可以通过在属性名后面加上?,表示当前属性为可选,如下:

image-20230926150946103

4.2.5 class 类型

使用 class 关键字来定义类,类中可以包含属性、构造方法、普通方法等

image-20230926151839881

在定义类时,可以使用 implments 关键字实现接口,如下:

image-20230926152034526

在定义类时,可以使用 extends 关键字 继承其他类,如下:

image-20230926152222634

typescript
//定义一个函数 print,打印到控制台,并且指定参数类型为string
function sout(msg: string) {
    console.log(msg)
}

//调用上面的函数,传递非string类型的参数
sout("123")

/* 1111111111111111111111111111111111111 */

//标注变量,指定变量 msg 的类型为string
let msg: string = 'hello ts!'
//标注参数和返回值,指定 m2 函数的参数类型 为string,并且返回值也为 string
const m2 = (name: string): string => {
    return name.toLowerCase() + msg
}
sout(m2('Abc '))


/* 1111111111111111111111111111111111111 */

//定义字符串类型的变量
let username: string = 'itcast'
//定义布尔类型的变量
let isTrue: boolean = false
//定义数字类型的变量
let age: number = 20
console.log(username)
console.log(isTrue)
console.log(age)

/* 1111111111111111111111111111111111111 */

//字面量类型,指定参数 alignment 的取值只能是 left、right、center
function printText(s: string, alignment: "left" | "right" | "center") {
    console.log(s, alignment)
}
printText('hello', 'left')
// printText('hello') //错误:取值只能是left丨right丨center


/* 1111111111111111111111111111111111111 */

//定义一个接口,名字为cat
interface Cat {
    name: string,
    age?: number //加上? 表示当前属性是可选的
}
//定义变量为cat类型
const c1: Cat = { name: 'xiaobai', age: 1 }
const c2: Cat = { name: 'xiaobai' } //错误:缺少 age 属性
//const c3: Cat = { name: 'xiaobai', age: 1, sex: '公' } //错误:多出 sex 属性

console.log(c1)


/* 1111111111111111111111111111111111111 */

//定义一个类,名称为User
class User {
    name: string //属性
    constructor(name: string) {//构造方法
        this.name = name
    }
    //方法
    study() {
        console.log(this.name + '正在学习...')
    }
}

const u = new User('张三')

console.log(u.name)
u.study()

/* 1111111111111111111111111111111111111 */

interface Animal {
    name: string
    eat(): void
}
//定义一个类Bird,实现上面的Animal接口
class Bird implements Animal {
    name: string
    constructor(name: string) {
        this.name = name
    }
    eat(): void {
        console.log(this.name + ' eat')
    }
}
//创建类型为Bird的对象
const b1 = new Bird('杜鹃')
console.log(b1.name)
b1.eat()



/* 1111111111111111111111111111111111111 */

//定义Parrot类,并且继承 Bird类
class Parrot extends Bird {
    parrotName:string
    constructor(parrotName:string){
        super(parrotName+'super')
        this.parrotName = parrotName
    }
    sayName(): void {
        console.log(this.name + ' say hello')
    }
    sayParrotName(): void {
        console.log(this.parrotName + ' say hello')
    }
}

const myParrot = new Parrot('Polly')
myParrot.sayName()
myParrot.sayParrotName()
myParrot.eat()

二、Vue知识点和工具类总结

更新: 2025/4/9 字数: 0 字 时长: 0 分钟

1、vue.config.js代理配置-跨域

js
    proxy: { //配置跨域,通常发生在前端应用和后端API服务器不在同一个域(域名、协议或端口不同)的情况下。
      '/api': { ///api是一个前缀,所有以/api开头的请求都会被代理到target指定的服务器。
        target: process.env.VUE_APP_URL,
        ws: false, // 是否启用WebSockets代理
        secure: false, //指定是否验证SSL证书,如果是https接口,需要配置这个参数
        changeOrigin: true, //是否改变源地址,用于指定是否更改请求的来源。默认为false
        pathRewrite:{ //重写路径,用于重写请求的路径
          '^/api':''
        }
      }
    }

2、箭头函数

箭头函数和普通函数在语法和行为上有一些重要的区别:

  1. 语法:箭头函数的语法更简洁,它使用=>符号来定义函数。例如,下面是一个箭头函数:

    javascript
    const add = (a, b) => a + b;

    相比之下,这是一个普通的函数:

    javascript
    function add(a, b) {
      return a + b;
    }
  2. **this绑定:箭头函数不绑定自己的this值,它会捕获其所在上下文的this值。这意味着箭头函数的this值在定义时就已经确定,不会在调用时改变。**例如:

    javascript
    const obj = {
      value: 42,
      getValue: function() {
        return () => this.value;
      }
    };
    
    const value = obj.getValue()();
    console.log(value); // 42

    在这个例子中,箭头函数getValue返回了一个箭头函数,这个箭头函数的this值是getValue函数的this值,也就是obj对象。所以,当箭头函数被调用时,它返回obj对象的value属性。

    相比之下,如果getValue是一个普通的函数,那么箭头函数的this值会是getValue函数被调用的上下文的this值,可能不是obj对象。

  3. **构造函数:箭头函数不能用作构造函数,**也就是说,你不能使用new关键字来创建箭头函数的实例。例如:

    javascript
    const Animal = () => {};
    const animal = new Animal(); // TypeError: Animal is not a constructor

    在这个例子中,当你尝试使用new关键字来创建Animal函数的实例时,JavaScript会抛出一个错误,因为Animal是一个箭头函数,不能用作构造函数。

总的来说,箭头函数在语法上更简洁,**并且有特殊的this绑定行为,这使得它们在某些情况下比普通函数更合适。然而,箭头函数也有一些限制,比如不能用作构造函数,**所以在使用箭头函数时需要考虑这些

3、form表单案例-新增

vue
<template>
  <div class="addBrand-container">
    <div class="container">
      <el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="180px">
        <!-- ref 是一个特殊的属性,用于给 DOM 元素或组件实例设置引用,this.$refs.ruleForm-->
        <el-form-item label="账号" prop="username">
          <el-input v-model="ruleForm.username"></el-input>
        </el-form-item>
        <el-form-item label="员工姓名" prop="name">
          <el-input v-model="ruleForm.name"></el-input>
        </el-form-item>
        <el-form-item label="手机号" prop="phone">
          <el-input v-model="ruleForm.phone"></el-input>
        </el-form-item>
        <el-form-item label="性别" prop="sex">
          <el-radio v-model="ruleForm.sex" label="1">男</el-radio> <!-- label 属性指定了单选框的值。 -->
          <el-radio v-model="ruleForm.sex" label="2">女</el-radio>
        </el-form-item>
        <el-form-item label="身份证号" prop="idNumber">
          <el-input v-model="ruleForm.idNumber"></el-input>
        </el-form-item>

        <!-- 保存 -->
        <div class="subBox">
          <el-button type="primary" @click="submitForm('ruleForm', false)">保存</el-button>
          <!-- type="primary": 这是 Element UI 的按钮组件属性,表示按钮的样式类型为“主要”按钮,通常用来突出显示。 -->
          <el-button v-if="this.optType === 'add'" type="primary" @click="submitForm('ruleForm', true)">保存并继续添加员工
          </el-button>
          <el-button @click="() => this.$router.push('/employee')">返回</el-button>
        </div>

      </el-form>
    </div>
  </div>
</template>

<script lang="ts">
import { validate } from 'schema-utils';
import { addEmployee } from '@/api/employee';


export default {
  data() {
    return {
      optType: 'add', //操作类型,add表示新增,edit表示编辑

      ruleForm: {
        username: '',
        name: '',
        phone: '',
        sex: '1',
        idNumber: '',
      },
      rules: { //表单校验规则
        name: [
          { required: true, message: '请输入员工姓名', trigger: 'blur' }, /* trigger: 'blur' 表示失去焦点触发 */
          { min: 2, max: 10, message: '长度在 2 到 10 个字符', trigger: 'blur' }
        ],
        username: [
          { required: true, message: '请输入账号', trigger: 'blur' },
          { min: 2, max: 10, message: '长度在 2 到 10 个字符', trigger: 'blur' }
        ],
        phone: [
          { min: 11, max: 11, message: '手机号码必须是11位!', trigger: 'blur' },

          {
            required: true, trigger: 'blur', validator: (rule, value, callback) => { /* validator验证器 */
              if (value === '' || (/^1(3|4|5|6|7|8|9)\d{9}$/.test(value) === false)) { //利用正则表达式判断手机号格式
                callback(new Error('请输入正确的手机号'));
              } else {
                callback();
              }
            }
          }

        ],
        idNumber: [
          { min: 15, max: 18, message: '身份证号码位数不正确!', trigger: 'blur' },
          {
            required: true, trigger: 'blur', validator: (rule, value, callback) => { /* validator验证器 */
              if (value === '' || (/^\d{17}(X|x)$|(^\d{15}$)|(^\d{18}$)/.test(value) === false)) { //利用正则表达式判断身份证号格式
                callback(new Error('请输入正确的身份证号'));
              } else {
                callback();
              }
            }
          }
        ]
      }
    }
  },

  methods: {
    //提交表单数据
    submitForm(formName, isContinue) {
      //表单数据校验
      this.$refs[formName].validate((valid) => {
        if (valid) { //校验通过
          //添加员工
          addEmployee(this.ruleForm).then((res) => {
            if (res.data.code === 1) {
              this.$message.success('添加成功!')

              if (isContinue) { //如果isContinue为true,则继续添加
                this.$refs[formName].resetFields(); //重置表单
              } else { //如果isContinue为false,则返回员工列表
                this.$router.push('/employee')
              }

            } else { //添加失败,返回错误信息
              this.$message.error(res.data.msg)
            }
          })
        }
      })
    }
  }
}

</script>

<style lang="scss" scoped>
.addBrand {
  &-container {
    margin: 30px;
    margin-top: 30px;

    .HeadLable {
      background-color: transparent;
      margin-bottom: 0px;
      padding-left: 0px;
    }

    .container {
      position: relative;
      z-index: 1;
      background: #fff;
      padding: 30px;
      border-radius: 4px;

      // min-height: 500px;
      .subBox {
        padding-top: 30px;
        text-align: center;
        border-top: solid 1px $gray-5;
      }
    }

    .idNumber {
      margin-bottom: 39px;
    }

    .el-form-item {
      margin-bottom: 29px;
    }

    .el-input {
      width: 293px;
    }
  }
}
</style>

4、JavaScript的动态属性和Vue的响应式系统

vue
<script lang="ts">
import { validate } from 'schema-utils';
import { addEmployee, getEmployeeById ,updateEmployee} from '@/api/employee';


export default {
  data() {
    return {
      optType: 'add', //操作类型,add表示新增,edit表示修改

      //表单数据结构
      ruleForm: {
        username: '',
        name: '',
        phone: '',
        sex: '1',
        idNumber: '',
      }
    }
  }


  created() { //在组件创建时,根据路由参数判断操作类型 add 或者 update
    this.optType = this.$route.query.id ? 'update' : 'add' //路径参数id存在,则为update,否则为add
    if (this.optType === 'update') { //如果是update,则根据id获取员工信息
      getEmployeeById(this.$route.query.id).then((res) => {
        if (res.data.code === 1) { //修改操作,需要根据id查询原始数据,用于回显
          this.ruleForm = res.data.data
        }
      })
    }
  },

在 Vue.js 中,当你在 created 钩子中通过 API 调用来赋值 this.ruleForm 时,你可能会发现 this.ruleForm 可以访问到从服务器返回的数据中的字段,即使这些字段在组件的初始数据定义中没有列出。这种情况的原因可以归结为以下几点:

1. 动态属性

JavaScript 对象是动态的,你可以随时向对象添加新的属性。这意味着,当你从服务器返回的数据中包含新的字段时,它们会被添加到 this.ruleForm 对象上,即使这些字段没有在初始数据定义中声明

例如,初始时 ruleForm 可能定义为:

javascript
ruleForm: {
  username: '',
  name: '',
  phone: '',
  sex: '1',
  idNumber: ''
}

当你从 API 返回的数据中获取到 id 并赋值给 this.ruleForm

javascript
this.ruleForm = res.data.data

如果 res.data.data 中包含 id 字段,this.ruleForm 将会包含这个 id 字段,因为对象的属性是可以动态添加的。

2.Vue 的响应式系统

Vue 的响应式系统会自动跟踪对象的变化。当你将新的对象赋值给 this.ruleForm 时,Vue 会将这个新对象添加到响应式系统中。原本定义在 data 中的 ruleForm 只是初始的结构定义,它不限制对象的实际使用情况

3. 数据结构和原型

即使你在 data 中定义了一个初始结构,实际运行时 ruleForm 可能会包含来自 API 的额外字段。由于 JavaScript 对象的动态特性,这些字段会被添加到 ruleForm 对象上,而不影响对象的原型或定义的结构。你可以随时访问和使用这些字段。

4. 具体例子

假设 API 返回的数据如下:

javascript
{
  data: {
    code: 1,
    data: {
      username: 'john_doe',
      name: 'John Doe',
      phone: '1234567890',
      sex: '1',
      idNumber: '123456789012345678',
      id: 'abc123' // 新字段
    }
  }
}

当你执行:

javascript
this.ruleForm = res.data.data

this.ruleForm 将变成:

javascript
{
  username: 'john_doe',
  name: 'John Doe',
  phone: '1234567890',
  sex: '1',
  idNumber: '123456789012345678',
  id: 'abc123' // 额外的字段
}

因此,即使在初始定义中没有 id 字段,赋值后 this.ruleForm.id 依然是可访问的。

总结

  • JavaScript 对象是动态的,你可以随时添加和访问新的属性。
  • Vue 的响应式系统会处理所有对象的变化,包括动态添加的属性。
  • data 中的初始结构仅用于定义初始状态,实际数据可以包含更多字段。

通过理解这些原理,你可以更好地管理和使用你的数据对象,确保你的应用在处理动态数据时能正确响应和显示信息。

三、苍穹外卖-前端环境搭建、员工管理

更新: 2025/4/9 字数: 0 字 时长: 0 分钟

课程内容

  • 前端环境搭建

  • 员工分页查询

  • 启用禁用员工账号

  • 新增员工

  • 修改员工

1. 前端环境搭建

1.1 技术选型

本项目使用到的前端技术如下:

  • node.js
  • vue
  • ElementUI
  • axios
  • vuex
  • vue-router
  • typescript

1.2 熟悉前端代码结构

直接导入课程资料中提供的苍穹外卖项目前端初始工程,此工程中已经开发了部分功能,后续我们在此基础上开发新功能即可。

导入后的效果如下:

image-20231009112322302

主要代码在src目录中,src目录结构如下:

image-20231009112436925

下面重点介绍一下src中的重点文件和目录:

  • api:存放封装了Ajax请求文件的目录
  • components:公共组件存放目录
  • views:存放视图组件的目录
  • App.vue:项目的主组件,页面的入口文件
  • main.ts:整个项目的入口文件
  • router.ts:路由文件

注意:我们上面导入的初始工程中并没有前端项目运行所依赖的JS包,需要在终端通过 npm install 命令进行安装。安装完成后,可以看到生成了node_modules目录,目录中就是安装的JS包,效果如下:

image-20231009154944721

接下来,我们通过初始工程中已经完成的登录功能来跟踪程序的执行过程,熟悉前端项目的代码结构。

首先需要启动前端项目,命令为:npm run serve,然后访问前端项目,效果如下:

image-20231010103544159

注意:需要同时启动后端 java 服务,才能进行前后端交互。

具体操作的步骤如下:

① 获得登录页面路由地址

​ 登录页面完整的访问地址为 http: //localhost:8888/#/login,其中登录页面的路由地址为 /login,我们需要通过此路由地址找到对应的登录视图组件

② 从main.ts中找到路由文件

​ main.ts是整个前端项目的入口文件,在此文件中会创建Vue实例,在创建Vue实例时需要传入路由对象,所以从此文件中可以找到对应的路由文件位置。如下所示:

image-20231011101828271

③ 从路由文件中找到登录视图组件

​ 在路由文件中会配置整个项目所有的路由映射规则,我们只需要找到 /login 这个路径对应的实体组件即可。如下所示:

image-20231011102024084

④ 从登录视图组件中找到登录方法

​ 从上面的路由文件可以确定登录视图组件就是src/views/login/index.vue,此时就可以打开这个文件,然后仔细阅读代码,找到登录方法,如下所示:

image-20231011102341145

⑤ 跟踪登录方法的执行过程

​ 找到登录方法后,就需要跟踪代码的执行过程,主要就是观察前后端的交互过程。例如前端如何发送的Ajax请求,后端返回的数据格式等等

2. 员工分页查询

2.1 需求分析和接口设计

根据产品原型来了解需求,员工分页查询的产品原型如下:

image-20231011104301458

业务规则:

  • 根据页码展示员工信息(员工姓名、账号、手机号、账号状态、最后操作时间等)
  • 每页展示10条数据
  • 分页查询时可以根据需要,输入员工姓名进行查询

要展示员工分页数据,就需要前后端进行数据交互,对应的接口如下:

基本信息

Path: /admin/employee/page

Method: GET

请求参数

Query

参数名称是否必须示例备注
name张三员工姓名
page1页码
pageSize10每页记录数

返回数据

名称类型是否必须默认值备注其他信息
codenumber必须
msgnull非必须
dataobject必须
├─ totalnumber必须
├─ recordsobject []必须item 类型: object
├─ idnumber必须
├─ usernamestring必须
├─ namestring必须
├─ passwordstring必须
├─ phonestring必须
├─ sexstring必须
├─ idNumberstring必须
├─ statusnumber必须
├─ createTimestring,null必须
├─ updateTimestring必须
├─ createUsernumber,null必须
├─ updateUsernumber必须

2.2 代码开发

要开发前端代码,首先需要找到对应的组件。从路由文件 router.ts 中找到员工管理页面(组件)。

image-20231012100658784

可以看到,员工管理页面(组件)的位置为:src/views/employee/index.vue。我们只需要在此文件中开发员工分页查询相关的前端代码即可,整个开发过程大概可以分为以下几个关键步骤:

  1. 根据产品原型,制作页面头部效果(输入框、查询按钮等)
  2. 为查询按钮绑定单击事件,发送Ajax请求,查询员工分页数据,实现前后端交互
  3. 提供 vue 的初始化方法,在页面加载后就查询分页数据
  4. 使用ElementUI提供的表格组件展示分页数据
  5. 使用ElementUI提供的分页条组件实现翻页效果

注意:开发过程中,并不是所有的代码都实现了再测试,而是开发一部分,就需要测试一下,看效果,如果有问题再调整。没有问题,再继续开发、测试。所以,这是一个逐渐完善的过程。下面我们就按照上面的几个关键步骤来开发,每开发完一个关键步骤,就需要测试一下,来验证我们的代码是否正确。

2.2.1 制作页面头部效果

根据产品原型,制作页面头部效果(输入框、查询按钮等)。产品原型中的头部效果如下:

image-20231012104130496

注意:输入框和按钮都是使用 ElementUI 提供的组件,对于前端的组件只需要参考 ElementUI 提供的文档,进行修改即可。实现代码如下:

html
`<div class="tableBar">`
    `<label style="margin-right: 5px">`
        员工姓名: 
    `</label>`
    `<el-input placeholder="请输入员工姓名" style="width: 15%" />`
    `<el-button type="primary" style="margin-left: 20px">`查询`</el-button>`
    `<el-button type="primary" style="float: right">`+添加员工`</el-button>`
`</div>`

开发完上面代码后,就可以测试一下,页面效果如下:

image-20231012114155894

2.2.2 实现前后端数据交互

前面我们已经完成了页面头部效果开发,接下来就需要开发前后端数据交互的动态效果。

第一步:为查询按钮绑定单击事件

image-20231016094210211

第二步:在methods中定义 pageQuery 方法,先验证当前方法能否正常执行

image-20231016094354605

注意:按照开发规范,真正发送Ajax请求的代码需要封装到 api目录下的ts文件中(src/api/employee.ts)

第三步:在src/api/employee.ts 中定义 getEmployeeList 方法,实现发送Ajax请求获取分页数据

image-20231016094612536

注意:发送 Ajax 请求的URL地址需要和前面我们设计的分页查询接口对应

第四步:在员工管理组件中导入 employee.ts 中定义的方法,并在data() 方法中定义分页相关的模型数据

image-20231016094930398

注意:需要将name属性和上面的员工姓名输入框进行双向绑定,如下:

image-20231016095018274

第五步:在pageQuery 方法中调用 getEmployeeList 方法

image-20231016095152184

2.2.3 自动发送Ajax请求

前面的代码我们已经实现了前后端数据交互,但是有一个问题,就是只有在点击查询按钮时才会发生Ajax请求,实现分页数据查询。我们通常需要的是在当前页面(组件)加载后,就需要发送Ajax请求,查询第一页的数据。要实现这个效果,我们可以通过vue的生命周期方法,即created方法来做到,代码如下:

image-20231016095540340

2.2.4 使用表格展示分页数据

前面我们已经实现了前后端数据交互,现在就需要将后端返回的数据通过表格展示出来,我们可以使用ElementUI提供的表格组件,具体使用方法可以参照官方提供的示例 https://element.eleme.io/#/zh-CN/component/table

image-20231016100031134

2.2.5 使用分页条实现翻页效果

使用 ElementUI 提供的分页条组件,并绑定事件处理函数,具体使用方法可以参照官方提供的示例 https://element.eleme.io/#/zh-CN/component/pagination

image-20231016100300675

image-20231016100327628

2.3 功能测试

可以通过下面两种方式来测试:

  • 直接进行前后端联调,查看页面效果
  • 通过浏览器F12查看数据交互过程

3. 启用禁用员工账号

3.1 需求分析和接口设计

根据产品原型来进行需求分析:

image-20231016103554407

可以对状态为“启用” 的员工账号进行“禁用”操作

可以对状态为“禁用”的员工账号进行“启用”操作

状态为“禁用”的员工账号不能登录系统

接口设计如下:

基本信息

Path: /admin/employee/status/

Method: POST

请求参数

Headers

参数名称参数值是否必须示例备注
Content-Typeapplication/json

路径参数

参数名称示例备注
status1状态,1为启用 0为禁用

Query

参数名称是否必须示例备注
id员工id

返回数据

名称类型是否必须默认值备注其他信息
codeinteger必须format: int32
datastring非必须
msgstring非必须

3.2 代码开发

第一步:为启用、禁用按钮绑定单击事件

image-20231016154949134

第二步:编写对应的处理函数handleStartOrStop

image-20231016155041179

到此可以先测试一下,检查当前方法能否成功执行,页面效果如下:

image-20231016155124396

第三步:在 employee.ts 中封装启用禁用员工账号方法,发送Ajax请求

image-20231016155246041

注意:发送请求的方式和相关参数,必须和前面的接口设计保持一致

第四步:在员工管理组件中引入上面定义的enableOrDisableEmployee方法,并完善 handleStartOrStop 方法

image-20231016155426898

第五步:代码优化,如果是管理员账号则不允许更改账号状态

image-20231016155558430

3.3 功能测试

直接进行前后端联调,查看页面效果

通过浏览器F12查看数据交互过程

image-20231016152210849

4. 新增员工

4.1 需求分析和接口设计

根据产品原型来进行需求分析,产品原型如下:

image-20231017105355195

新增员工时需要录入 账号、员工姓名、手机号、性别、身份证号等信息。其中账号因为是登录凭证,所以必须是唯一的。手机号要求必须是合法的11位手机号码,身份证号为合法的18位身份证号码。

当新员工入职后,可以通过当前的新增员工功能来为新员工开通账号,新员工通过开通的账号来登录商家管理端系统。

接口设计如下:

基本信息

Path: /admin/employee

Method: POST

请求参数

Headers

参数名称参数值是否必须示例备注
Content-Typeapplication/json

Body

名称类型是否必须默认值备注其他信息
idinteger非必须员工idformat: int64
idNumberstring必须身份证
namestring必须姓名
phonestring必须手机号
sexstring必须性别
usernamestring必须用户名

返回数据

名称类型是否必须默认值备注其他信息
codeinteger必须format: int32
dataobject非必须
msgstring非必须

4.2 代码开发

要开发新增员工前端代码,首先需要了解添加员工业务功能的操作步骤:

  1. 在员工管理列表页面,点击 “添加员工”按钮,跳转到新增页面
  2. 在新增员工页面录入员工相关信息
  3. 点击“保存”按钮完成新增操作

接下来我们就可以按照上面的操作步骤来具体开发前端的代码。

第一步:为“添加员工”按钮绑定单击事件

image-20231017162453327

第二步:编写 handleAddEmp 方法,进行路由跳转

image-20231017162515941

注意:在路由文件中已经配置了如下路由规则

image-20231017162553386

第三步:根据产品原型,开发页面元素

image-20231017162651760

第四步:定义模型数据和表单校验规则

image-20231017162927506

image-20231017162808963

第五步:在 employee.ts 中封装新增员工方法,发送Ajax请求,实现前后端交互

image-20231017163005227

注意:

  • 发送请求的方式和相关参数,必须和前面的接口设计保持一致
  • 使用此方法时,别忘了在组件中通过import导入

第六步:在 methods 中定义提交表单的方法 submitForm

image-20231017163116948

4.3 功能测试

直接进行前后端联调,查看页面效果

通过浏览器F12查看数据交互过程

5. 修改员工

5.1 需求分析和接口设计

根据产品原型来进行需求分析,产品原型如下:

image-20231017105355195

修改员工时需要回显 账号、员工姓名、手机号、性别、身份证号等信息。其中账号因为是登录凭证,所以必须是唯一的。手机号要求必须是合法的11位手机号码,身份证号为合法的18位身份证号码。根据需要对数据进行修改即可。

修改员工功能涉及到2个接口,分别是根据id查询员工信息和根据id修改员工信息。具体接口设计如下:

(1)根据id查询员工 接口

基本信息

Path: /admin/employee/

Method: GET

请求参数

路径参数

参数名称示例备注
id100员工id

返回数据

名称类型是否必须默认值备注其他信息
codeinteger必须format: int32
dataobject必须
├─ createTimestring非必须format: date-time
├─ createUserinteger非必须format: int64
├─ idinteger非必须format: int64
├─ idNumberstring非必须
├─ namestring非必须
├─ passwordstring非必须
├─ phonestring非必须
├─ sexstring非必须
├─ statusinteger非必须format: int32
├─ updateTimestring非必须format: date-time
├─ updateUserinteger非必须format: int64
├─ usernamestring非必须
msgstring非必须

(2)根据id修改员工信息 接口

基本信息

Path: /admin/employee

Method: PUT

请求参数

Headers

参数名称参数值是否必须示例备注
Content-Typeapplication/json

Body

名称类型是否必须默认值备注其他信息
idinteger必须format: int64
idNumberstring必须
namestring必须
phonestring必须
sexstring必须
usernamestring必须

返回数据

名称类型是否必须默认值备注其他信息
codeinteger必须format: int32
datastring非必须
msgstring非必须

5.2 代码开发

要开发修改员工前端代码,首先需要了解修改员工业务功能的操作步骤:

  1. 在员工管理列表页面点击 “修改”按钮,跳转到修改页面
  2. 在修改员工页面录入员工相关信息
  3. 点击“保存”按钮完成修改操作

注意:

  • 由于添加员工和修改员工的表单项非常类似,所以添加和修改操作可以共用同一个页面(addEmployee.vue)
  • 修改员工涉及到原始数据回显,所以页面跳转时需要传递员工id作为参数

接下来我们就可以按照上面的操作步骤来具体开发前端的代码。

第一步:在员工管理页面中,为“修改”按钮绑定单击事件,用于跳转到修改页面

image-20231018153123546

第二步:在methods中编写 handleUpdateEmp 方法,实现路由跳转

image-20231018153201351

注意:使用路由对象的push方法在进行路由跳转时,可以通过地址栏传递参数,具体语法为:

javascript
this.$router.push({path: 路由路径, query:{参数名:参数值}})

第三步:在 addEmployee.vue 组件中定义模型数据optType,用于区分本次操作是新增还是修改

image-20231018153639687

注意:区分当前操作是新增员工还是修改员工,只需要尝试获取路由参数id。如果能获取到则当前操作为修改,否则为新增。具体获取路由参数的语法为:

javascript
this.$router.query.参数名

第四步:如果是修改操作,需要根据id查询员工原始信息用于页面回显,需要在 employee.ts 中创建queryEmployeeById方法

image-20231018154041788

注意:

  • 发送请求的方式和相关参数,必须和前面的接口设计保持一致
  • 使用此方法时,别忘了在组件中通过import导入

第五步:在 addEmployee.vue 组件的 created 方法中查询员工原始信息用于页面数据回显

image-20231018154225861

到目前位置已经实现了员工信息的回显,可以进行简单的测试,如果回显没有问题再继续开发后面的代码

第六步:如果是修改操作,“保存并继续添加员工” 按钮则不需要显示,通过 v-if 指令进行判断

image-20231018154415419

第七步:在 employee.ts 中创建方法,用于修改员工,发送Ajax请求

image-20231018154443989

注意:

  • 发送请求的方式和相关参数,必须和前面的接口设计保持一致
  • 使用此方法时,别忘了在组件中通过import导入

第八步:修改 addEmployee.vue 组件中的 submitForm 方法,需要根据当前操作类型执行新增或者修改操作

image-20231018154601719

5.3 功能测试

直接进行前后端联调,查看页面效果

通过浏览器F12查看数据交互过程

四、苍穹外卖-套餐管理

更新: 2025/4/9 字数: 0 字 时长: 0 分钟

课程内容

  • 套餐分页查询
  • 启售停售套餐
  • 删除套餐
  • 新增套餐

1. 套餐分页查询

1.1 需求分析和接口设计

根据产品原型来了解需求,套餐分页查询的产品原型如下:

image-20231019100236805

业务规则:

  • 根据页码展示套餐信息(套餐名称、套餐图片、套餐分类、价格、售卖状态、最后操作时间等)
  • 每页展示10条数据
  • 分页查询时可以根据需要,输入套餐名、套餐分类、售卖状态 进行查询

要展示套餐分页数据,就需要前后端进行数据交互,对应的接口有两个:

  • 分类查询接口(用于套餐分类下拉框中分类数据展示)
  • 套餐分页查询接口

(1)分类查询接口

基本信息

Path: /admin/category/list

Method: GET

请求参数

Query

参数名称是否必须示例备注
type2分类类型:1为菜品分类,2为套餐分类

返回数据

名称类型是否必须默认值备注其他信息
codeinteger必须format: int32
dataobject []非必须item 类型: object
├─ createTimestring非必须format: date-time
├─ createUserinteger非必须format: int64
├─ idinteger非必须format: int64
├─ namestring非必须
├─ sortinteger非必须format: int32
├─ statusinteger非必须format: int32
├─ typeinteger非必须format: int32
├─ updateTimestring非必须format: date-time
├─ updateUserinteger非必须format: int64
msgstring非必须

(2)套餐分页查询接口

基本信息

Path: /admin/setmeal/page

Method: GET

请求参数

Query

参数名称是否必须示例备注
categoryId分类id
name套餐名称
page页码
pageSize每页记录数
status套餐起售状态

返回数据

名称类型是否必须默认值备注其他信息
codenumber必须
msgnull非必须
dataobject非必须
├─ totalnumber非必须
├─ recordsobject []非必须item 类型: object
├─ idnumber必须
├─ categoryIdnumber必须
├─ namestring必须
├─ pricenumber必须
├─ statusnumber必须
├─ descriptionstring必须
├─ imagestring必须
├─ updateTimestring必须
├─ categoryNamestring必须

1.2 代码开发

要开发前端代码,首先需要找到对应的组件。从路由文件 router.ts 中找到套餐管理页面(组件)。

image-20231020143129575

可以看到,套餐管理页面(组件)的位置为:src/views/setmeal/index.vue。我们只需要在此文件中开发套餐分页查询相关的前端代码即可,整个开发过程大概可以分为以下几个关键步骤:

  1. 根据产品原型,制作页面头部效果(输入框、下拉框、查询按钮等)
  2. 动态填充套餐分类下拉框中的分类数据
  3. 为查询按钮绑定单击事件,发送Ajax请求,查询套餐分页数据,实现前后端交互
  4. 提供 vue 的初始化方法,在页面加载后就查询分页数据
  5. 使用ElementUI提供的表格组件展示分页数据
  6. 使用ElementUI提供的分页条组件实现翻页效果

注意:开发过程中,并不是所有的代码都实现了再测试,而是开发一部分,就需要测试一下,看效果,如果有问题再调整。没有问题,再继续开发、测试。所以,这是一个逐渐完善的过程。下面我们就按照上面的几个关键步骤来开发,每开发完一个关键步骤,就需要测试一下,来验证我们的代码是否正确。

2.2.1 制作页面头部效果

根据产品原型,制作页面头部效果(输入框、查询按钮等)。产品原型中的头部效果如下:

image-20231020153003427

注意:输入框和按钮都是使用 ElementUI 提供的组件,对于前端的组件只需要参考 ElementUI 提供的文档,进行修改即可。实现代码如下:

image-20231020153413719

注意:当前套餐分类下拉框中的数据是直接在页面固定写死的,后续需要改为从后端动态获取。

2.2.2 动态填充套餐分类下拉框数据

现在需要将套餐分类下拉框中的数据改为动态获取,即前端需要发送Ajax请求,调用后端的分类查询接口,然后将后端返回的套餐分类数据动态展示在下拉框中。因为本次前后端交互是需要查询分类数据,所以按照项目规范,发送Ajax请求的代码需要定义到 src/api/category.ts 文件中。其实在此文件中已经定义了此方法,如下:

image-20231020153956869

所以,此处只需要将此方法(getCategoryByType)导入当前组件,然后在 created 方法中调用此方法,获取套餐分类数据,动态填充套餐分类下拉框即可。具体代码如下:

image-20231020154449782

注意:因为此处我们要查询的是套餐分类,所以传递的参数type值为2。

前面我们已经初步实现了页面头部制作,并且可以填充下拉框中的数据了。但是命名上并不是特别规范,所以我们需要进行一个调整,具体修改后端的代码如下:

image-20231020154747092

两个下拉框的测试效果如下:

image-20231020154909513

2.2.3 动态获取套餐分页数据

前面我们已经完成了页面头部效果开发,接下来就需要开发前后端数据交互的动态效果。

第一步:为查询按钮绑定单击事件

image-20231020155054149

第二步:在methods中定义 pageQuery 方法,先验证当前方法能否正常执行

image-20231020155113565

注意:按照开发规范,真正发送Ajax请求的代码需要封装到 api目录下的ts文件中(src/api/setMeal.ts)

第三步:在src/api/setMeal.ts 中定义 getSetmealPage 方法,实现发送Ajax请求获取分页数据

image-20231020155235487

注意:发送 Ajax 请求的URL地址需要和前面我们设计的分页查询接口对应

第四步:在套餐管理组件中导入 setMeal.ts 中定义的方法,并在data() 方法中定义分页相关的模型数据

image-20231020155347403

注意:需要将属性和上面的输入框、下拉框进行双向绑定。

第五步:在pageQuery 方法中调用 getSetmealPage方法,实现前后端数据交互

image-20231020155526957

2.2.4 自动发送Ajax请求

前面的代码我们已经实现了前后端数据交互,但是有一个问题,就是只有在点击查询按钮时才会发生Ajax请求,实现分页数据查询。我们通常需要的是在当前页面(组件)加载后,就需要发送Ajax请求,查询第一页的数据。要实现这个效果,我们可以通过vue的生命周期方法,即created方法来做到,代码如下:

image-20231020155637956

2.2.5 使用表格展示分页数据

前面我们已经实现了前后端数据交互,现在就需要将后端返回的数据通过表格展示出来,我们可以使用ElementUI提供的表格组件,具体使用方法可以参照官方提供的示例 https://element.eleme.io/#/zh-CN/component/table

image-20231020155734122

2.2.6 使用分页条实现翻页效果

使用 ElementUI 提供的分页条组件,并绑定事件处理函数,具体使用方法可以参照官方提供的示例 https://element.eleme.io/#/zh-CN/component/pagination

image-20231020155812305

1.3 功能测试

可以通过下面两种方式来测试:

  • 直接进行前后端联调,查看页面效果
  • 通过浏览器F12查看数据交互过程

image-20231020155938508

2. 启售停售套餐

2.1 需求分析和接口设计

根据产品原型来进行需求分析:

image-20231023094228836

可以对状态为“启售” 的套餐进行“停售”操作

可以对状态为“停售”的套餐进行“启售”操作

状态为“停售”的套餐不展示在用户端小程序中,所以用户不能购买停售的套餐

接口设计如下:

基本信息

Path: /admin/setmeal/status/

Method: POST

请求参数

Headers

参数名称参数值是否必须示例备注
Content-Typeapplication/json

路径参数

参数名称示例备注
status1套餐状态,1表示起售,0表示停售

Query

参数名称是否必须示例备注
id101套餐id

返回数据

名称类型是否必须默认值备注其他信息
codeinteger必须format: int32
dataobject非必须
msgstring非必须

2.2 代码开发

第一步:为启售停售按钮绑定单击事件

image-20231023095123819

第二步:编写对应的处理函数handleStartOrStop

image-20231023095249208

到此可以先测试一下,检查当前方法能否成功执行,页面效果如下:

image-20231023095322300

第三步:在 setMeal.ts 中封装套餐起售停售方法,发送Ajax请求

image-20231023095630819

注意:发送请求的方式和相关参数,必须和前面的接口设计保持一致

第四步:在套餐管理组件中引入上面定义的enableOrDisableSetmeal方法,并完善 handleStartOrStop 方法

image-20231023095730185

注意:

  • 在进行套餐启售停售操作时,建议先弹出确认框,用户点击确定按钮后再进行前后端交互
  • 在传递套餐状态参数status时,需要进行简单的处理,即:如果当前套餐状态值为1,则传递过去的参数为0;如果当前套餐状态值为0,则传递过去的参数为1

2.3 功能测试

直接进行前后端联调,查看页面效果

通过浏览器F12查看数据交互过程

image-20231023100008708

3. 删除套餐

3.1 需求分析和接口设计

根据产品原型来进行需求分析,产品原型如下:

image-20231023142125944

  • 点击 删除 按钮,删除指定的一个套餐
  • 勾选需要删除的套餐,点击 批量删除 按钮,删除选中的一个或多个套餐
  • 状态为 “启售” 的套餐不能删除,需要给出操作提示

可以看到,删除套餐功能在操作时有两种方式。一种是点击【删除】按钮,可以删除对应的一个套餐;一种是勾选需要删除的套餐,然后点击【批量删除】按钮,可以删除勾选的多个套餐。我们在设计接口时可以兼容这两种不同的操作方式,也就是只需要一个接口即可。

接口设计如下:

基本信息

Path: /admin/setmeal

Method: DELETE

请求参数

Query

参数名称是否必须示例备注
ids1,2,3ids

返回数据

名称类型是否必须默认值备注其他信息
codeinteger非必须format: int32
dataobject非必须
msgstring非必须

3.2 代码开发

要开发删除套餐前端代码,首先需要了解删除套餐业务功能的操作步骤:

  1. 在套餐管理列表页面,点击 【删除】按钮,或者勾选套餐然后点击【批量删除】按钮,弹出确认对话框
  2. 点击确认对话框中的【确定】按钮,则执行删除操作。如果套餐状态为“启售”,则不能删除,弹出信息提示
  3. 点击确认对话框中的【取消】按钮,则关闭对话框,不执行删除操作

接下来我们就可以按照上面的操作步骤来具体开发前端的代码。

第一步:在 setMeal.ts 中封装删除套餐方法,发送Ajax请求,用于实现前后端交互

image-20231023163921668

注意:

  • 发送请求的方式和相关参数,必须和前面的接口设计保持一致
  • 使用此方法时,别忘了在组件中通过import导入

第二步:为【批量删除】按钮绑定单击事件,并在methods中编写对应的处理函数

image-20231023164110268

image-20231023164255891

到目前为止我们点击【批量删除】按钮,是可以执行handleDelete方法的。接下来我们需要解决一个问题,就是当前选中了哪些套餐呢?我们需要能够动态获取到,因为我们需要将这些套餐的id作为参数传递到后端。

第三步:参考 ElementUI 的官方文档,为表格组件添加 selection-change 事件和对应的处理函数,通过此事件我们就可以动态获取到当前勾选的套餐有哪些

image-20231023165937804

image-20231023170202159

注: selection-change 事件为 当选择项发生变化时触发的事件

第四步:完善 handleDelete 方法,获取当前被选中的行,并进行参数准备

image-20231023170312864

注意:单个删除 和 批量删除,都是调用 handleDelete 方法,所以此方法还需要进一步调整

第五步:为【删除】按钮绑定单击事件,处理函数还是 handleDelete

image-20231023170440585

注意:

  • 在 handleDelete 方法中通过第一个参数来区分是单个删除还是批量删除
  • S表示单个删除,B表示批量删除

第六步:调整 handleDelete 方法,使其兼容单个删除和批量删除

image-20231023170612705

第七步:完善 handleDelete 方法,进行相应提示

image-20231023170710212

注:

  • 批量删除时,如果没有选中套餐,给出提示
  • 删除之前需要弹出确认框,让用户确认

3.3 功能测试

直接进行前后端联调,查看页面效果

通过浏览器F12查看数据交互过程

image-20231023170832442

4. 新增套餐

4.1 需求分析和接口设计

根据产品原型来进行需求分析,产品原型如下:

image-20231025113638867

image-20231025113707572

新增套餐时需要录入套餐名称、所属分类、套餐价格、套餐包含的菜品、套餐图片、描述等信息。其中套餐包含的菜品需要在弹出的添加菜品窗口中勾选。在弹出的添加菜品窗口中需要按照分类来展示菜品。

新增套餐功能涉及到4个接口,分别是:

  • 根据类型查询分类 接口
  • 根据分类查询菜品 接口
  • 文件上传 接口
  • 新增套餐 接口

(1) 根据类型查询分类 接口

基本信息

Path: /admin/category/list

Method: GET

请求参数

Query

参数名称是否必须示例备注
type2分类类型:1为菜品分类,2为套餐分类

返回数据

名称类型是否必须默认值备注其他信息
codeinteger必须format: int32
dataobject []非必须item 类型: object
├─ createTimestring非必须format: date-time
├─ createUserinteger非必须format: int64
├─ idinteger非必须format: int64
├─ namestring非必须
├─ sortinteger非必须format: int32
├─ statusinteger非必须format: int32
├─ typeinteger非必须format: int32
├─ updateTimestring非必须format: date-time
├─ updateUserinteger非必须format: int64
msgstring非必须

(2) 根据分类查询菜品 接口

基本信息

Path: /admin/dish/list

Method: GET

请求参数

Query

参数名称是否必须示例备注
categoryId101分类id

返回数据

名称类型是否必须默认值备注其他信息
codeinteger必须format: int32
dataobject []非必须item 类型: object
├─ categoryIdinteger非必须format: int64
├─ createTimestring非必须format: date-time
├─ createUserinteger非必须format: int64
├─ descriptionstring非必须
├─ idinteger非必须format: int64
├─ imagestring非必须
├─ namestring非必须
├─ pricenumber非必须
├─ statusinteger非必须format: int32
├─ updateTimestring非必须format: date-time
├─ updateUserinteger非必须format: int64
msgstring非必须

(3) 文件上传 接口

基本信息

Path: /admin/common/upload

Method: POST

请求参数

Headers

参数名称参数值是否必须示例备注
Content-Typemultipart/form-data

Body

参数名称参数类型是否必须示例备注
filefile文件

返回数据

名称类型是否必须默认值备注其他信息
codeinteger必须format: int32
datastring必须文件上传路径
msgstring非必须

(4) 新增套餐 接口

基本信息

Path: /admin/setmeal

Method: POST

接口描述:

请求参数

Headers

参数名称参数值是否必须示例备注
Content-Typeapplication/json

Body

名称类型是否必须默认值备注其他信息
categoryIdinteger必须分类idformat: int64
descriptionstring非必须套餐描述
idinteger非必须套餐idformat: int64
imagestring必须套餐图片
namestring必须套餐名称
pricenumber必须套餐价格
setmealDishesobject []必须套餐包含的菜品item 类型: object
├─ copiesinteger必须份数format: int32
├─ dishIdinteger必须菜品idformat: int64
├─ idinteger非必须套餐和菜品关系idformat: int64
├─ namestring必须菜品名称
├─ pricenumber必须菜品价格
├─ setmealIdinteger必须套餐idformat: int64
statusinteger必须套餐状态:1位起售 0为停售format: int32

返回数据

名称类型是否必须默认值备注其他信息
codeinteger必须format: int32
dataobject非必须
msgstring非必须

4.2 代码解读

新增套餐操作步骤:

①点击 “新建套餐”按钮,跳转到新增页面

②在新增套餐页面录入套餐相关信息

③点击“保存”按钮完成新增操作

首先需要找到新增套餐页面,可以通过操作过程来找:

第一步:在套餐管理列表页面中找到【新建套餐】按钮,查看按钮绑定的事件和对应的处理函数

image-20231026101005983

第二步:在methods中找到handleAdd函数,查看跳转的路由路径

image-20231026101037315

第三步:在路由文件中找到此路径对应的视图组件,可以看到是src/views/setmeal/addSetmeal.vue

image-20231026101135442

第四步:解读src/views/setmeal/addSetmeal.vue这个文件即可

4.3 功能测试

直接进行前后端联调,查看页面效果

通过浏览器F12查看数据交互过程

五、【nvm】适合小白的 nvm 安装配置教程(Windows版)

更新: 2025/4/9 字数: 0 字 时长: 0 分钟

nvm 简介

nvm 全称为 node.js version management,顾名思义是用于管理多个 nodejs 的版本控制工具。通过 nvm 可以安装和切换不同版本的 nodejs。nvm 主要是为了解决 node.js 各种版本存在不兼容现象。

一、nvm 的安装与配置

1、install nvm

🔎 nvm 官方下载地址.

🔎 nvm1.1.12 百度网盘地址【不保证最新版本哈】.

image-20231117165521095.

image-20231117105855047.

image-20231117110141183.

2、base command【主要验证 nvm 是否安装成功】

architecture:架构

shell
# 查看 nvm 版本,version 可简写成 小v
nvm version
nvm v
# 显示 node 是运行在 32 位还是 64 位
nvm arch

image-20231116104021164.

重头戏来啦

shell
# 显示已经安装的列表,list 可简化为 ls
nvm list
# 显示远程可安装的列表,list 也可简化为 ls
nvm list available
# 卸载指定版本 node
nvm uninstall [version]
# 使用指定版本 node
nvm use [version]

image-20231116105530304.

image-20231116105919918.

玩玩就好了,基本不会用到

shell
# 开启 node.js 版本管理
nvm on
# 关闭 node.js 版本管理
nvm off

image-20231116104926531.

3、configure taobao mirror【配置下载镜像】

nvm 的安装路径下,找到 settings.txt,在后面加上这两行,设置国内淘宝镜像源:

tex
node_mirror: https://npmmirror.com/mirrors/node/
npm_mirror: https://npmmirror.com/mirrors/npm/

提醒阿里云开源镜像站https://npm.taobao.org/【该域名在 2022.06.30 号正式下线和停止 DNS 解析,新域名npmmirror.com

image-20231116110240298.

image-20240202205301489.

问题:为什么要配置镜像?

答:配置完国内的镜像后,npm install xxx 的下载速度会很快!

二、node 的安装与配置

1、install node

shell
nvm list available
nvm install 16
nvm list
nvm use 16

小提示:不要安装 node 的奇数版本!LTS 一般就是偶数版本。

image-20231116111322142.

image-20231117090853114.

2、修改 npm 默认镜像源【新增内容】

方式一:手动修改

  1. 修改 npm 镜像源为淘宝镜像源
sh
npm config set registry http://registry.npmmirror.com
  1. 检查是否成功
sh
npm config get registry

image-20240202212343592.

方式二:安装 nrm

前提:需要先安装 node。 请参考下 nrm 安装使用教程 这篇文章.

3、配置 node 的 prefix(全局路径)和 cache(缓存路径)

3.1、【非必须,可直接跳过】选择需要使用的 node 版本,新建两个文件夹

image-20231116132013883.

image-20231116132131610.

每次使用 nvm 安装 node 都需要手动创建这两个文件夹吗?

答:并不是每次都需要自己手动创建,但是第一次推荐你手动创建,以后当你设置 prefix 和 cache 后,系统会自动创建。

node_global:npm install 下载的全局插件

node_cache:node 的缓存

3.2、【关键】设置全局模板(prefix)和缓存文件(cache)的存放路径:

shell
# npm config set cache "%NVM_SYMLINK%\node_cache"
npm config set cache "D:\nvm\nodejs\node_cache"
shell
# npm config set prefix "%NVM_SYMLINK%\node_global"
npm config set prefix "D:\nvm\nodejs\node_global"
shell
# 可编辑 .npmrc 配置文件
npm config edit
shell
# 查看部分 .npmrc 配置信息
npm config ls

image-20231116133715188.

image-20231116133858389.

注意:

  1. 引号里面换自己的路径
  2. 引号里面的路径是软链接(nodejs)的路径

每次使用 nvm 安装 node 都需要配置 prefix 和 cache 吗?

答:并不需要每次都设置。配置的 prefix 和 cache 的信息都在 C:\Users\zhuang\.npmrc 文件里

4、配置 node 环境变量(通用配置,仅需一次配置)

版本一:【推荐】简单

image-20231117125538903.

版本二:【跳过就行】复杂

问题:每次使用 nvm 安装 node 都需要配置环境变量吗?

答:配置时多注意注意 NODE_PATH 值的配置就可以避免多次配置环境变量。不然每次更换 node 都需要配置环境变量。

NVM_HOME:nvm 地址目录

NVM_SYMLINK:nvm 配置 nodejs 的软链接,nvm use 版本号 时会自动创建

安装 nvm 时 NVM_HOMENVM_SYMLINK 一般会自动配置上,不需要我们手动配置,非常舒心!

NODE_PATH:需要万分注意!!!,格式是:NVM_SYMLINK 路径 + \node_global

这样配置的话你就只需要配置一次,以后切换其他版本的 node 时也不需要重新配置 NODE_PATH 环境变量,方便使用。

注意:下图中 NODE_PATH 的值需要手动配置,其中 %NVM_SYMLINK% 需要换上具体的值,然后加上 \node_global

eg. 我的 NODE_PATH 配置为

tex
D:\nvm\nodejs\node_global

image-20231117102641335.

image-20231117125804868.

5、配置完成后,输入以下命令进行验证

shell
# 查看 node 版本,若返回版本号,node 可用
node -v
shell
# 查看 npm 版本,若返回版本号,npm 可用
npm -v

如果返回了相应的版本号,则证明成功。

image-20231116131617422.

三、【选学】扩展:nvm 如何实现 node 任意切换

image-20231117104701081.

image-20231117105022521.

image-20231117105328963