苍穹外卖(前端)
创建前端工程:
环境要求:
基于脚手架创建前端工程,需要具备如下环境要求:
node.js:前端项目的运行环境
npm:JavaScript 的包管理工具
Vue CLI:基于 Vue 进行快速开发的完整系统,实现交互式的项目脚手架

操作过程:
使用 Vue CLI 创建前端工程
方式一:vue create 项目名称




方式二:vue ui






工程结构:
| 文件/目录 | 介绍 |
| node_modules | 当前项目依赖的 js 包 |
| assets | 静态资源存放目录 |
| components | 公共组件存放目录 |
| App.vue | 项目的主组件,页面的入口文件 |
| main.js | 整个项目的入口文件 |
| package.json | 项目的配置信息、依赖包管理 |
| vue.config.js | vue-cli 配置文件 |
启动服务:
使用 VS Code 打开创建的前端工程,启动前端工程:


访问前端工程:

在 vue.config.js 中配置前端服务端口号防止冲突:


Vue 使用方式:
Vue 组件:
Vue 的组件文件以 .vue 结尾,每个组件由三部分组成:
结构 <template>,样式 <style>,逻辑 <script>

文本插值:
作用:用来绑定 data 方法返回的对象属性
用法:{{插值表达式}}
<template> <div> <h1>{{ name }}</h1> <h1>{{ age > 60 ? '老年' : '青年' }}</h1> </div> </template> <script> export default { data() { return { name: '张三', age: 30 } } } </script>
属性绑定:
作用:为标签的属性绑定 data 方法中返回的属性
用法:v-bind:xxx,简写为 :xxx
<template> <div> <div><input type="text" v-bind:value="name"></div> <div><input type="text" :value="age"></div> <div><img :src="src"/></div> </div> </template> <script> export default { data() { return { name: '李四', age: 30, src: 'https://img2.baidu.com/it/u=3423222222,2822222222&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500' } } } </script>事件绑定:
作用:为元素绑定对应的事件
用法:v-on:xxx,简写为 @xxx
<template> <div> <div> <input type="button" value="保存" v-on:click="handleSave"/> <input type="button" value="保存" @click="handleSave"/> </div> </div> </template> <script> export default { data() { return { name: '王五' } }, methods: { handleSave(){ alert(this.name) } } } </script>

双向绑定:
作用:表单输入项和 data 方法中的属性进行绑定,任意一方改变都会同步给另一方
用法:v-mode
<template> <div> <div> 双向绑定: {{ name }} <input type="text" v-model="name"/> <input type="button" value="改变" @click="handleChange"/> </div> </div> </template> <script> export default { data() { return { name: '张三' } }, methods: { handleChange(){ this.name = '李四' } } } </script>

条件渲染:
作用:根据表达式的值来动态渲染页面元素
用法:v-if、v-else、v-else-if
<template> <div> <div v-if="sex == 1"> 男 </div> <div v-else-if="sex == 2"> 女 </div> <div v-else> 未知 </div> </div> </template> <script> export default { data() { return { sex: 1 } } } </script>
Axios:
Axios 是一个基于 promise 的网络请求库,作用于浏览器和 node.js 中,使用 Axios 可以在前端项目中发送各种方式的 http 请求
安装命令:npm install axios

导入:import axios from 'axios'

参数说明:url:请求路径;data:请求体数据,最常见的是 Json 格式数据;config:配置对象,可以设置查询参数、请求头信息
注意:在使用 axios 时,经常会遇到跨域问题;为了解决该问题,可以在 vue.config.js 文件中配置代理:
const { defineConfig } = require('@vue/cli-service') module.exports = defineConfig({ transpileDependencies: true, devServer: { port: 7070, proxy: { '/api': { target: 'http://localhost:8080', pathRewrite: { '^/api': '' } } } } })Axios 的 Post 请求示例:
axios.post('/api/admin/employee/login',{ username:'admin', password: '123456' }).then(res => { console.log(res.data) }).catch(error => { console.log(error.response) })Axios 的 Get 请求示例:
axios.get('/api/admin/shop/status',{ headers: { token: ‘xxx.yyy.zzz’ } })Axios 提供的统一使用方式示例(可以发送各种方式的请求):
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) })路由 Vue-Router:
介绍:
Vue 属于单页面应用,所谓路由,就是根据浏览器路径不同,用不同的视图组件替换这个页面内容
在 Vue 应用中使用路由功能,需要安装 Vue-Router:





创建完带有路由功能的前端项目后,在工程中会生成一个路由文件用于路由的配置:

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



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

具体配置方式:
在路由文件中配置路由路径和视图的对应关系:
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在视图组件中配置 router-link 标签,用于生成超链接:
<router-link to="/">Home</router-link> | <router-link to="/about">About</router-link> | <router-link to="/test">Test</router-link> |在视图组件汇总配置 router-view 标签:
<template> <div> <nav> <router-link to="/">Home</router-link> | <router-link to="/about">About</router-link> | <router-link to="/test">Test</router-link> | </nav> <!-- 视图组件展示的位置 --> <router-view/> </div> </template>要实现路由跳转,可以通过标签式和编程式两种方式:
标签式:
<router-link to="/about">About</router-link>编程式:
<template> <div> <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>嵌套路由:
嵌套路由:组件内要切换内容,就需要用到嵌套路由(子路由),效果如下:
在 App.vue 视图组件中有 <router-view> 标签,其他视图组件可以展示在此

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

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

实现步骤:
安装并导入 Elementui,实现页面布局(Container 布局容器):

ContainerView.vue:
<template> <el-container> <el-header>Header</el-header> <el-container> <el-aside>Aside</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:
<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 中配置路由映射规则(嵌套路由配置):
const routes = [ { path: '/', name: 'home', component: HomeView }, { path: '/about', name: 'about', // route level code-splitting // this generates a separate chunk (about.[hash].js) for this route // which is lazy-loaded when the route is visited. component: () => import(/* webpackChunkName: "about" */ '../views/AboutView.vue') }, { path: '/404', component: () => import('../views/404View.vue') }, { path: '/c', component: () => import('../views/container/ContainerView.vue'), redirect: '/c/p1', //嵌套路由(子路由),对应的组件会展示在当前组件内部 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') } ] }, { path: '*', redirect: '/404' } ]在 ContainerView.vue 布局容器视图中添加 <router-view>,实现子视图组件展示:
<template> <el-container> <el-header>Header</el-header> <el-container> <el-aside>Aside</el-aside> <el-main> <router-view/> </el-main> </el-container> </el-container> </template>在 ContainerView.vue 布局容器视图中添加 <router-link>,实现路由请求:
<template> <el-container> <el-header>Header</el-header> <el-container> <el-aside> <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> <el-main> <router-view/> </el-main> </el-container> </el-container> </template>
状态管理 Vuex:
介绍:
Vuex 是一个专为 Vue.js 应用程序开发的状态管理库;可以在多个组件之间共享数据,并且共享的数据是响应式的,即数据的变更能及时渲染到模板;它采用集中式存储管理所有组件的状态
Vuex 应用的核心是 store(仓库),包含着你的应用中大部分的状态(state),改变 store 中的状态的唯一途径就是显式地提交(commit)mutation
安装 Vuex:npm install vuex@next --save
| 核心概念 | 说明 |
| state | 状态对象,集中定义各个组件共享的数据 |
| mutations | 类似于一个事件,用于修改共享数据,要求必须是同步函数 |
| actions | 类似于 mutation,可以包含异步操作,通过调用 mutation 来改变共享数据 |
使用方式:
第一步:创建带有 Vuex 功能的前端项目




在创建的前端工程中,自动创建了相关的文件:

在前端项目的入口文件中创建 Vue 实例时,需要将 store 对象传入 :

第二步:在 src/store/index.js 文件中集中定义和管理共享数据
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: { } })第三步: 在视图组件中展示共享数据

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

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

第六步:如果在修改共享数据的过程中有异步操作,则需要将异步操作的代码编写在 actions 的函数中
//集中管理多个组件共享的数据 export default new Vuex.Store({ //集中定义共享数据 state: { name: '未登录游客' }, getters: { }, //通过当前属性中定义的函数修改共享数据,必须都是同步操作 mutations: { setName(state,newName) { state.name = newName } }, //通过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) } }) } }, modules: { } })第七步:在视图组件中调用 actions 中定义的函数




TypeScript:
介绍:
TypeScript(简称:TS) 是微软推出的开源语言,是 JavaScript 的超集,TypeScript = Type + JavaScript(在 JS 基础上增加了类型支持),TypeScript 文件扩展名为 ts;TypeScript 可编译成标准的 JavaScript,并且在编译时进行类型检查

安装命令:npm install -g typescript
TS 属于静态类型编程语言,在编译期做类型检查(可在编写代码的同时发现错误);而 JS 属于动态类型编程语言,在执行期做类型检查
创建基于 TS 的前端工程:






常用类型:
| 类型 | |
|---|---|
| 字符串类型 | string |
| 数字类型 | number |
| 布尔类型 | boolean |
| 数组类型 | number[],string[], boolean[] 等 |
| 任意类型 | any |
| 复杂类型 | type 与 interface |
| 函数类型 | () => void |
| 字面量类型 | "a"|"b"|"c" |
| class 类 | class Animal |
类型标注的位置:
标注变量、标注参数、标注返回值
//标注变量,指定变量 msg 的类型为string let msg:string = 'hello ts !' //标注参数和返回值,指定 m2 函数的参数类型 为string,并且返回值也为 string const m2 = (name:string):string => { return name.toLowerCase() + msg }字符串、数字、布尔类型:
//定义字符串类型的变量 let username: string = 'zhu' //定义布尔类型的变量 let isTrue: boolean = false //定义数字类型的变量 let age: number = 19 console.log(username) console.log(isTrue) console.log(age)字面量类型:
// 字面量类型,指定参数 alignment 的取值只能是 left、right、center function printText(s: string, alignment: "left" | "right" | "center") { console.log(s, alignment) } printText("hello", "left") printText("hello", "aaa") // 错误:取值只能是 left | right | centerinterface 类型:
//定义接口Cat,规定对象必须包含name(string)和age(number)属性 interface Cat { name: string, age: number } //合法:属性完全匹配接口 const c1: Cat = { name: '耄耋', age: 1 } //错误示例: //const c2: Cat = { name: '大开门' } //缺少age属性 //const c3: Cat = { name: '听泉', age: 1, sex: '公' } //多出sex属性 console.log(c1)//接口Cat中,age为可选属性(用?标记) interface Cat { name: string, age?: number // 可选属性:可以存在或不存在 } //合法:可选属性age可省略 const c1: Cat = { name: '耄耋' } //合法:可选属性age也可包含 const c2: Cat = { name: '大开门', age: 1 } //错误示例: //const c3: Cat = { name: '听泉', age: 1, sex: '公' } //多出sex属性class 类型:
//定义类User,包含属性和方法 class User { name: string; //类的属性 //构造方法:创建实例时初始化属性 constructor(name: string) { this.name = name } //类的方法 study() { console.log(`${this.name}正在学习`) } } //创建User类的实例 const u = new User('张三') console.log(u.name) //输出:张三 u.study() //输出:张三正在学习//定义接口Animal,规定必须包含name属性和eat方法 interface Animal { name: string eat(): void } //类Bird实现接口Animal,必须实现接口的所有属性和方法 class Bird implements Animal { name: string; constructor(name: string) { this.name = name } //实现接口的eat方法 eat(): void { console.log(this.name + ' eat') } } //创建Bird实例 const b1 = new Bird('鸽子') console.log(b1.name) //输出:鸽子 b1.eat() //输出:鸽子 eat//Parrot类继承Bird类,自动获得Bird的属性和方法 class Parrot extends Bird { //新增自己的方法 say(): void { console.log(this.name + ' say hello') } } //创建Parrot实例 const myParrot = new Parrot('Polly') myParrot.say() //输出:Polly say hello(自己的方法) myParrot.eat() //输出:Polly eat(继承自Bird的方法)