# 综合项目实战
# 回顾
- 脚手架用法
- 理解前端工程化(配置webpack很繁琐)
- Vue脚手架的好处:快速创建一个现成的项目架构
- 熟悉脚手架项目的目录结构
- main.js
- App.vue
- HelloWord.vue
- 理解相关文件的代码
- 掌握ES6模块化的导入导出规则
- 掌握单文件组件的代码结构
- template 组件的模板
- script 组件的数据和逻辑处理
- style 组件的样式
- 前后端交互
- 熟悉axios这个js的基本信息
- axios基本用法
- axios.get()
- 传参:直接通过url地址传递参数(拼接字符串到url中)
- 传参:通过params熟悉进行传参(本质上在发送请求时也会进行字符串拼接)
- axios.post()
- axios()
- 支持各种请求方式:get/post/put/delete/patch
- axios.get()
- 案例
- 图书列表的展示
- 删除图书
- 添加图书
- 关键字搜索(函数防抖限制发送请求的频率)
- 加载状态的控制
# axios拦截器
- 发送请求之前,先经过请求拦截器,这里可以对请求参数做一些调整
- 请求返回数据之前,先经过响应拦截器,这里可以对返回的数据进行处理
// 拦截器
// 添加一个请求拦截器
axios.interceptors.request.use(function (config) {
// console.log('-------------------')
// axios({method: 'get', url: '', data: {}})
// config表示请求相关的配置选项(参数)
// console.log(config)
// config.url = 'http://localhost:3000/b'
// 请求发送之前可以做一些事情
return config;
}, function (error) {
// 请求如果发生错误,该函数会触发,这里可以进行处理
return Promise.reject(error);
});
// 添加一个响应拦截器
axios.interceptors.response.use(function (response) {
// 只要响应的结果状态码是200开头的都表示成功,该方法会触发,这里可以对返回的数据进行处理
// console.log('===================')
// response表示axios封装的一个对象,里面包括后台返回的原始数据
// response.data表示后台返回的原始数据
console.log(response)
return response;
}, function (error) {
// 只要响应的结果状态码不是200开头的都表示失败,该方法会触发,这里可以提示错误信息
return Promise.reject(error);
});
# 项目介绍
项目整体功能:图书管理;人员管理;楼层管理
- 图书管理
- 图书列表
- 添加图书
- 删除图书
- 修改图书
# 初始化项目
目标:能够基于VueCli创建项目
- 通过vue create命令创建项目
vue create mybook
- 进入项目跟目录
cd mybook
- 运行项目
npm run serve
# 页面基本布局
目标:能够基于Bootstrap实现案例的基本布局结构

- 分析页面的组件结构
- Login.vue 登录组件 (Login.vue和主页面是互斥的)
- NavBar.vue 顶部导航组件
- Aside.vue 左侧菜单组件
- BookList.vue 英雄列表组件
- BookAdd.vue 添加英雄组件
- BookEdit.vue 编辑英雄组件
- PersonList.vue 人员列表组件
- FloorList.vue 楼层列表组件
- 安装bootstrap包
npm i bootstrap@3.3.7
- 导入样式
// 引入bootstrap
import 'bootstrap/dist/css/bootstrap.min.css'
# 路由配置
目标:实现点击侧边栏的连接。需要切换右侧的内容。这个需要路由来实现。
- 安装vue-router
- 脚手架环境下,一般都是安装npm包,而不是单独引入一个js文件
npm i vue-router
- 导入路由
- 采用ES6模块化方式导入VueRouter
import VueRouter from 'vue-router'
- 注册路由(配置路由插件)
- 插件的规则我们后续详细再讲,现在这样理解如下的代码:采用这种方式让路由在脚手架环境下生效。
Vue.use(VueRouter)
- 准备路由组件
- Login.vue
- Home.vue
- 配置路由规则
- 建立组件和url地址的关联关系(访问一个连接,展示一个组件)
import Login from './components/Login.vue'
import Home from './components/Home.vue'
// 配置组件的路由映射
const routes = [
{ path: '/', redirect: '/login' },
{ path: '/login', component: Login },
{ path: '/home', component: Home }
]
实例化路由对象
- 让上述配置的映射关系生效
const router = new VueRouter({ routes })
- 挂载路由对象到Vue实例上
- 这样可以让上述配置的路由规则在Vue中生效
new Vue({
router,
render: h => h(App)
}).$mount('#app')
- 配置路由填充位
App.vue根组件
<div id='app'>
<!-- 路由对应组件显示的位置 -->
<router-view></router-view>
</div>
# 导航组件
实现顶部导航组件
- 拆分组件实现布局
NavBar.vue
<template>
<nav class="navbar navbar-inverse">
<a class="navbar-brand" href="#">CURD</a>
</nav>
</template>
<script>
export default {
name: 'NavBar'
}
</script>
- 导入组件并使用
<!-- 组件的模板HTML -->
<template>
<div id="app" class="container">
<!-- 顶部导航栏组件 -->
<NavBar/>
</div>
</template>
<!-- 组件的配置选项JS -->
<script>
// 导入另外一个组件
import NavBar from './components/NavBar.vue'
export default {
// 组件的名称,方便调试使用
name: 'App',
components: {
NavBar
}
}
</script>
# 侧边栏组件
拆分侧边栏组件布局
- 组件模板布局
<template>
<div class="col-md-2">
<div class="row">
<div class="list-group">
<a href="#" class="list-group-item active">图书列表</a>
<a href="#" class="list-group-item">人员列表</a>
<a href="#" class="list-group-item">楼层列表</a>
</div>
</div>
</div>
</template>
- 导入组件用法
<template>
<div class="container">
<!-- 在脚手架的环境下,使用如下的两种命名方式都可以 -->
<!-- <NavBar/> -->
<!-- 顶部导航栏组件 -->
<nav-bar/>
<!-- 左侧菜单组件:单个单词的组件名称不可以使用纯小写的方式,但是首字符大写是可以的 -->
<aside-menu/>
</div>
</template>
<script>
// 导入子组件
import NavBar from './components/NavBar.vue'
import AsideMenu from './components/Aside.vue'
export default {
// 配置局部子组件
components: {
NavBar,
AsideMenu
}
}
</script>
# 控制路由跳转菜单激活
目标:实现路由跳转菜单高亮控制
激活(高亮):点击哪一个链接,哪一个链接应该添加一个类名active,没有点中的链接标签去掉类名
实现原理:基于vue-router的相关配置,自动给链接标签添加指定的类名
// linkExactActiveClass属性的作用:控制点中链接标签后添加的类名名称,默认的名称是router-link-exact-active
const router = new VueRouter({ routes , linkExactActiveClass: 'active'})
# 组件内部样式处理
- 关于scoped
scoped 作用域 ,作用是把样式变成组件的局部样式(当前组件有效)
原理:随机给类名生成了一个唯一的属性选择器,如下图所示

# 拆分路由模块
目标:
main.js的职责足够单一,让代码更好维护。全局资源导入,根实例初始化。
- 封装路由模块:
src/router/index.js
import Vue from 'vue'
// 导入路由构造函数
import VueRouter from 'vue-router'
// @符号指的是src目录(@是src目录的别名)
// vue文件的后缀可以省略
// 导入路由组件
import Login from '@/components/Login.vue'
import Home from '@/components/Home.vue'
import BookList from '@/components/BookList.vue'
import PersonList from '@/components/PersonList.vue'
import FloorList from '@/components/FloorList.vue'
import BookAdd from '@/components/BookAdd.vue'
import BookEdit from '@/components/BookEdit.vue'
// 配置Vue的路由插件(让VueRouter发挥作用)
Vue.use(VueRouter)
// 配置组件的路由映射
const routes = [
{ path: '/', redirect: '/login' },
{ path: '/login', component: Login },
{
path: '/home',
component: Home,
children: [
{ path: 'books', component: BookList },
{ path: 'add', component: BookAdd },
{ path: 'edit', component: BookEdit },
{ path: 'persons', component: PersonList },
{ path: 'floors', component: FloorList }
]
}
]
// 实例化路由组件
const router = new VueRouter({
// 该属性作用:点击路由连接后,自动添加的高亮类名
// 点击router-link,vue-router会自动添加一个类名,默认类名router-link-exact-active
// 但是这个默认类名可以通过如下属性进行配置
linkExactActiveClass: 'active',
routes
})
// 导入路由对象
export default router
- 导入路由模块
import Vue from 'vue'
import App from './App.vue'
// 引入bootstrap
import 'bootstrap/dist/css/bootstrap.min.css'
// 导入路由实例(如果文件名称是index.js,可以省略)
import router from '@/router/index.js'
Vue.config.productionTip = false
new Vue({
render: h => h(App),
// 挂载路由实例
router
}).$mount('#app')
# 准备接口
目标:基于json-server模拟接口。
- 创建db.json文件
{
"books": [
{ "id": 1, "bname": "西游记", "author": "吴承恩", "ctime": "2020-03-21 10:38:20" },
{ "id": 2, "bname": "红楼梦", "author": "曹雪芹", "ctime": "2020-03-21 10:38:20" },
{ "id": 3, "bname": "三国演义", "author": "罗贯中", "ctime": "2020-03-21 10:38:20" }
]
}
- 启动接口
json-server db.json
测试接口地址:http://localhost:3000/books
- 安装axios包
npm i axios
# 图书列表组件
目标:实现英雄列表组件
- 组件基本布局
<template>
<div>
<a href="heroes-form.html" class="btn btn-primary">添加图书</a>
<hr>
<table class="table table-hover">
<thead>
<tr>
<th>ID</th>
<th>图书名称</th>
<th>出版时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for='item in books' :key='item.id'>
<td>{{item.id}}</td>
<td>{{item.bookname}}</td>
<td>{{item.createTime}}</td>
<td>
<button class="btn btn-success">编辑</button>
<button class="btn btn-danger">删除</button>
</td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
export default {}
</script>
<style>
</style>
- 在组件初始化时,获取英雄列表数据
// 导入axios库
import axios from 'axios'
// 配置axios请求的基准路径
axios.defaults.baseURL = 'http://localhost:3000/'
export default {
data () {
return {
// 图书列表数据
books: []
}
},
methods: {
loadBookList () {
// 加载图书列表数据
axios.get('books').then(ret => {
if (ret.status === 200) {
this.books = ret.data
}
})
}
},
mounted () {
this.loadBookList()
}
}
- 根据list数据,进行模板渲染。
<tbody>
<tr v-for='item in books' :key='item.id'>
<td>{{item.id}}</td>
<td>{{item.bookname}}</td>
<td>{{item.createTime}}</td>
<td>
<button class="btn btn-success">编辑</button>
<button class="btn btn-danger">删除</button>
</td>
</tr>
</tbody>
# 添加图书组件
目标:实现添加图书功能
- 添加链接跳转
<a @click='handleAdd' href="javascript:;" class="btn btn-primary">添加图书</a>
handleAdd () {
// 通过编程式导航跳转到添加图书的界面
this.$router.push('/home/add')
},
- 创建组件,基础布局
src/components/BooksAdd.vue
<template>
<form action="" method="post" role="form">
<legend>添加图书</legend>
<div class="form-group">
<label>图书名称</label>
<input type="text" class="form-control">
</div>
<div class="form-group">
<label>图书作者</label>
<input type="text" class="form-control">
</div>
<button type="submit" class="btn btn-primary">提交</button>
</form>
</template>
<script>
export default {}
</script>
<style>
</style>
- 收集英雄表单数据
<form @submit.prevent='handleSubmit' action="" method="post" role="form">
<legend>添加图书</legend>
<div class="form-group">
<label>图书名称</label>
<input v-model='bookname' type="text" class="form-control">
</div>
<div class="form-group">
<label>发布时间</label>
<input v-model='createTime' type="text" class="form-control">
</div>
<button type="submit" class="btn btn-primary">提交</button>
</form>
- 在处理函数中,进行添加,如果添加成功,跳转列表
handleSubmit () {
// 调用接口添加图书
handleSubmit () {
// 添加图书
this.axios.post('books', {
bookname: this.bookname,
createTime: this.createTime
}).then(ret => {
if (ret.status === 201) {
// 添加成功,跳转列表页面
this.$router.push('/home/books')
}
})
}
}
- 统一导入axios (
main.js)
// 统一导入axios
import axios from 'axios'
// 配置axios的基准路径
axios.defaults.baseURL = 'http://localhost:3000/'
// 可以把axios添加到Vue原型对象上,这样的所有组件都可以得到axios
// 所有的组件都是Vue的实例对象(实例对象都可以访问原型上的成员)
Vue.prototype.axios = axios
# 编辑图书组件
目标:实现编辑图书功能
- 实现动态跳转:
components/BooksList.vue
<router-link :to="'/heroes/edit/'+item.id" class="btn btn-success">编辑</router-link>
- 准备编辑组件,基础布局。
src/views/HeroesEdit.vue
<template>
<form @submit.prevent='handleSubmit' action="" method="post" role="form">
<legend>添加图书</legend>
<div class="form-group">
<label>图书名称</label>
<input v-model='bookname' type="text" class="form-control">
</div>
<div class="form-group">
<label>图书作者</label>
<input v-model='author' type="text" class="form-control">
</div>
<button type="submit" class="btn btn-primary">提交</button>
</form>
</template>
<script>
export default {}
</script>
<style>
</style>
- 配置路由规则,通过动态路由规则,来指向编辑组件。
src/router.js
// 导入组件
import BookEdit from '../components/BookEdit.vue'
// 路由映射配置
{path: '/books/edit/:id', component: BookEdit},
- 绑定表单数据
export default {
data () {
return {
id: -1,
bookname: '',
author: ''
}
}
}
- 组件初始化,获取当前英雄的信息,展示在表单中。
methods: {
loadBookInfo () {
// 获取图书的id
const id = this.$route.params.id
// 根据图书的id查询图书的详细信息
axios.get('http://localhost:3000/books/' + id).then((ret) => {
// 填充表单
this.bookname = ret.data.bname
this.author = ret.data.author
this.id = ret.data.id
})
}
},
created () {
// 组件显示时自动触发
this.loadBookInfo()
}
- 点击修改按钮,发起修改,修改成功后,列表组件。
<form @submit.prevent='handleSubmit' action="" method="post" role="form">
handleSubmit () {
// 调用接口实现图书的编辑操作
// 增删改查 post delete put get
axios.put('http://localhost:3000/books/' + this.id, {
bname: this.bookname,
author: this.author,
ctime: new Date()
}).then((ret) => {
if (ret.status === 200) {
// 编辑成功后跳转到列表页面
this.$router.push('/books')
}
}).catch(() => {
alert('编辑图书失败')
})
}
}
# 删除功能
目标:实现删除图书功能
- 绑定删除按钮点击事件
- 弹出确认框
- 点击确认,发送删除请求
- 删除成功,更新当前列表
<button @click="handleDelete(item.id)" class="btn btn-danger">删除</button>
handleDelete (id) {
if (!confirm('要删除图书吗?')) return
// 删除图书
this.axios.delete('/books/' + id).then(ret => {
if (ret.status === 200) {
// 删除成功,刷新列表
this.loadBookList()
}
})
}
# 时间过滤器
目标:基于moment包实现时间格式化过滤器
- 安装 moment
npm i moment
- 导入moment
BooksList.vue
// 导入moment
import moment from 'moment';
- 定义过滤器
filters: {
formatTime (time) {
// 我们希望时间编程 XXXX年XX月XX日
// const d = new Date(time)
// return d.getFullYear() + '年' + (d.getMonth() + 1) + '月' + d.getDate() + '日'
return moment(time).format('yyyy年MM月DD日 hh:mm:ss')
}
},
- 使用过滤器
<td>{{item.ctime|formatTime}}</td>
# axios全局配置
目标:全局配置axios
- 在组件中使用axios
- 每一个组件其实也是vue实例
- 实例都可以访问当构造函数的原型(方法|属性)
- 所以,把axios挂载到Vue的原型上
- 将来,任何组件都可以访问axios
// 进行axios的全局挂载
import axios from 'axios'
// 将来通过vue的实例访问$http,其实就是axios。
Vue.prototype.$http = axios
// 通过组件的实例对象this可以访问到$http
this.$http.delete('books/' + id)
# 回顾
- axios中拦截器用法(请求拦截器和响应拦截器)
- 熟悉组件化拆分思想
- 配置bootstrap样式
- 配置项目的整体路由(嵌套路由)
- 准备组件模板
- 准备二级路由的组件
- 控制路由链接的高亮效果
- 关于样式scoped属性的作用
- 图书列表展示
- 添加图书
- 删除图书
- 通过过滤器实现日期的格式化
- 关于组件的name属性:主要用于调试
- 编辑图书
# 编辑功能
# 编辑图书-上
- 绑定编辑按钮的事件,传递id路由参数,跳转到编辑图书组件
toEdit (id) {
// 跳转到编辑图书的页面
this.$router.push('/home/edit?id=' + id)
},
- 编辑图书组件中需要获取路由参数id,然后根据id查询图书的详细数据,回填表单
methods: {
handleSubmit () {
},
loadBookInfo (id) {
// 根据id查询图书的详细数据
this.$http.get('books/' + id).then(ret => {
if (ret.status === 200) {
// 获取数据成功,更新数据(初始化表单)
this.bookname = ret.data.bookname
this.createTime = ret.data.createTime
}
})
}
},
mounted () {
this.loadBookInfo(this.$route.query.id)
}
# 编辑图书-下
- 修改完成表单数据后,提交表单,跳转到列表页面
handleSubmit () {
// 编辑图书提交表单
this.$http.put('/books/' + this.id, {
bookname: this.bookname,
createTime: this.createTime
}).then(ret => {
if (ret.status === 200) {
// 编辑成功,跳转到列表页面
this.$router.push('/home/books')
}
})
},
# 组件的生命周期
组件的生命周期:一个事物(组件)从出现到消亡的整个过程
所有的生命周期函数都是自动触发,不要去手动调用
1、何时触发;2、有什么用
- 创建阶段
- beforeCreate 表示组件刚开始创建,尚未有组件的相关属性存在
- created 表示data属性已经存在,但是data可能还没有数据
- beforeMount 刚准备好组件的模板,但是尚未渲染到页面
- mounted 已经把模板渲染到了页面中
- 主要应用场景:调用接口用created;操作DOM用mounted
- 更新阶段(数据(data、props和路由参数)的变化导致更新钩子函数触发)
- beforeUpdate 组件数据更新前触发
- updated 组件数据更新后触发
- 主要应用场景:控制组件更新额状态提示
- 销毁阶段(组件从页面中移除)
- beforeDestroy 组件销毁前触发
- destroyed 组件销毁后触发
- 主要应用场景:组件销毁时,清理不再使用的相关的资源(比如定时任务)