Vue基础-Day07

综合项目实战

项目介绍

项目整体功能:图书管理;人员管理;楼层管理

  • 图书管理
    • 图书列表
    • 添加图书
    • 删除图书
    • 修改图书

image.png

总结:主要做图书的增删改查,会使用路由,并且整合登录组件

初始化项目

目标:能够基于VueCli创建项目

  1. 通过vue create命令创建项目
  1. vue create mydemo
  1. 进入项目跟目录
  1. cd mydemo
  1. 运行项目
  1. npm run serve

页面基本布局

目标:能够基于Bootstrap实现案例的基本布局结构

  • 分析页面的组件结构
    • NavBar.vue 顶部导航组件
    • Aside.vue 左侧菜单组件
    • BookList.vue 英雄列表组件
    • BookAdd.vue 添加英雄组件
    • BookEdit.vue 编辑英雄组件
    • PersonList.vue 装备列表组件
    • FloorList.vue 技能列表组件
  • 安装bootstrap包
  1. npm i bootstrap@3.3.7
  • 导入样式
  1. // 引入bootstrap
  2. import 'bootstrap/dist/css/bootstrap.min.css'

总结:

  1. 我们准备的布局基于Bootstrap的,并且有版本号要求
  2. 导入样式的方式采用import,而不是在HTML页面中link引入

路由配置

目标:实现点击侧边栏的连接。需要切换右侧的内容。这个需要路由来实现。

  1. 安装vue-router
  1. npm i vue-router
  1. 导入路由
  1. import VueRouter from 'vue-router'
  1. 注册路由(配置路由插件)
  1. Vue.use(VueRouter)
  1. 准备路由组件
  • Login.vue
  • Home.vue
  1. 配置路由规则
  1. // 导入路由组件
  2. import Login from './views/Login.vue'
  3. import Home from './views/Home.vue'
  4. // 配置路由映射
  5. const routes = [
  6. { path: '/', redirect: '/login' },
  7. { path: '/login', component: Login },
  8. { path: '/home', component: Home }
  9. ]
  1. 实例化路由对象
  1. // 创建路由对象
  2. const router = new VueRouter({
  3. routes
  4. })
  1. 挂载路由对象到Vue实例上
  1. new Vue({
  2. render: h => h(App),
  3. router
  4. }).$mount('#app')
  1. 配置路由填充位App.vue
  1. <template>
  2. <div>
  3. <!-- 路由填充位:Login.vue/Home.vue -->
  4. <router-view></router-view>
  5. </div>
  6. </template>

总结:

  1. 在脚手架环境下配置路由
  2. 配置一级路由组件Login.vue和Home.vue

实现基本的登录跳转

目标:熟悉编程式导航应用场景

  1. export default {
  2. data () {
  3. return {
  4. uname: '',
  5. pwd: ''
  6. }
  7. },
  8. methods: {
  9. handleLogin () {
  10. // 调用接口实现登录
  11. if (this.uname === 'admin' && this.pwd === 'admin') {
  12. // 登录成功,跳转到主页面
  13. this.$router.push('/home')
  14. } else {
  15. alert('用户名或者密码错误')
  16. }
  17. }
  18. }
  19. }

总结:登录时,调用接口后,判断成功与否,如果成功跳转到主页(编程式导航)

导航组件

实现顶部导航组件

  • 拆分组件实现布局 NavBar.vue
  1. <template>
  2. <nav class="navbar navbar-inverse">
  3. <a class="navbar-brand" href="#">CURD</a>
  4. </nav>
  5. </template>
  • 导入组件并使用 Home.vue
  1. <!-- 组件的模板HTML -->
  2. <template>
  3. <div id="app" class="container">
  4. <!-- 顶部导航栏组件 -->
  5. <NavBar/>
  6. </div>
  7. </template>
  8. <!-- 组件的配置选项JS -->
  9. <script>
  10. // 导入另外一个组件
  11. import NavBar from './components/NavBar.vue'
  12. export default {
  13. components: {
  14. NavBar
  15. }
  16. }
  17. </script>

总结:局部组件的使用流程:导入组件;配置组件;使用组件。

侧边栏组件

拆分侧边栏组件布局

  • 组件模板布局
  1. <template>
  2. <div class="col-md-2">
  3. <div class="row">
  4. <div class="list-group">
  5. <a href="#" class="list-group-item active">图书列表</a>
  6. <a href="#" class="list-group-item">人员列表</a>
  7. <a href="#" class="list-group-item">楼层列表</a>
  8. </div>
  9. </div>
  10. </div>
  11. </template>
  • 导入组件用法
  1. <template>
  2. <div class="container">
  3. <!-- 在脚手架的环境下,使用如下的两种命名方式都可以 -->
  4. <!-- <NavBar/> -->
  5. <!-- 顶部导航栏组件 -->
  6. <nav-bar/>
  7. <!-- 左侧菜单组件:单个单词的组件名称不可以使用纯小写的方式,但是首字符大写是可以的 -->
  8. <aside-menu/>
  9. </div>
  10. </template>
  11. <script>
  12. // 导入子组件
  13. import NavBar from './components/NavBar.vue'
  14. import AsideMenu from './components/Aside.vue'
  15. export default {
  16. // 配置局部子组件
  17. components: {
  18. NavBar,
  19. AsideMenu
  20. }
  21. }
  22. </script>

总结:局部组件的用法,导入组件;配置组件;使用组件

配置二级路由

目标:熟悉嵌套路由的应用场景

  • 在主页路由组件中再次配置路由填充位,左侧导航菜单使用router-link标签实现跳转
  1. <template>
  2. <div class="col-md-2">
  3. <div class="row">
  4. <div class="list-group">
  5. <a href="#" class="list-group-item active">英雄列表</a>
  6. <a href="#" class="list-group-item"></a>
  7. <a href="#" class="list-group-item">技能列表</a>
  8. <router-link>图书列表</router-link>
  9. <router-link>人员列表</router-link>
  10. <router-link>楼层列表</router-link>
  11. </div>
  12. </div>
  13. </div>
  14. </template>
  15. <div class="col-md-10">
  16. <!-- 右侧内容区:二级路由填充位 -->
  17. <router-view></router-view>
  18. </div>

总结:熟悉嵌套路由的应用场景

控制路由跳转菜单激活

目标:实现路由跳转菜单高亮控制

激活(高亮):点击哪一个链接,哪一个链接应该添加一个类名active,没有点中的链接标签去掉类名

实现原理:基于vue-router的相关配置,自动给链接标签添加指定的类名

  1. // linkExactActiveClass属性的作用:控制点中链接标签后添加的类名名称,默认的名称是router-link-exact-active
  2. const router = new VueRouter({ routes , linkExactActiveClass: 'active'})
  • 如果不加linkExactActiveClass: ‘active’属性配置,需要如何控制点中高亮?需要自己给router-link-exact-active类名添加样式
  1. <style>
  2. .router-link-exact-active {
  3. color: red !important;
  4. }
  5. </style>

总结:

  1. 定制类名,基于Bootstrap提供类名active控制高亮
  2. 自己给router-link-exact-active类名添加高亮样式

拆分路由模块

目标:main.js 的职责足够单一,让代码更好维护。全局资源导入,根实例初始化。

  • 封装路由模块:src/router/index.js
  1. // 拆分路由映射模块
  2. import Vue from 'vue'
  3. import VueRouter from 'vue-router'
  4. Vue.use(VueRouter)
  5. // 导入路由组件
  6. // @表示src目录
  7. import Login from '@/views/Login.vue'
  8. import Home from '@/views/Home.vue'
  9. import BookList from '@/views/BookList.vue'
  10. import PersonList from '@/views/PersonList.vue'
  11. import FloorList from '@/views/FloorList.vue'
  12. // 配置路由映射
  13. const routes = [
  14. { path: '/', redirect: '/login' },
  15. { path: '/login', component: Login },
  16. {
  17. path: '/home',
  18. component: Home,
  19. redirect: '/home/books',
  20. children: [
  21. { path: 'books', component: BookList },
  22. { path: 'persons', component: PersonList },
  23. { path: 'floors', component: FloorList }
  24. ]
  25. }
  26. ]
  27. // 创建路由对象
  28. const router = new VueRouter({
  29. // 自定义vue-router默认点中链接后添加的类名
  30. // 类名的默认值是router-link-exact-active
  31. linkExactActiveClass: 'active',
  32. routes
  33. })
  34. export default router
  • 导入路由模块
  1. // 导入路由模块
  2. import router from '@/router/index.js'
  3. new Vue({
  4. render: h => h(App),
  5. // 挂载路由实例
  6. router
  7. }).$mount('#app')

总结:简化入口文件的代码,路由模块可以单独维护。

准备接口

目标:基于json-server模拟接口。

  • 创建db.json文件
  1. { "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" } ]}
  • 启动接口
  1. # 注意:需要在db.json文件所在的路径执行命令json-server db.json# 测试接口地址:http://localhost:3000/books
  • 安装axios包
  1. npm i axios

总结:

  1. 模拟后端接口
  2. 安装axios包,用于项目中调用接口

图书列表组件

目标:实现图书列表组件

  • 组件基本布局
  1. <template>
  2. <div class="list-container">
  3. <a href="heroes-form.html" class="btn btn-primary">添加英雄</a>
  4. <hr />
  5. <table class="table table-hover">
  6. <thead>
  7. <tr>
  8. <th>ID</th>
  9. <th>英雄名称</th>
  10. <th>英雄性别</th>
  11. <th>创建时间</th>
  12. <th>操作</th>
  13. </tr>
  14. </thead>
  15. <tbody>
  16. <tr>
  17. <td>101</td>
  18. <td>亚索</td>
  19. <td></td>
  20. <td>2019-02-10 10:50:12</td>
  21. <td>
  22. <button class="btn btn-success">编辑</button>&nbsp;
  23. <button class="btn btn-danger">删除</button>
  24. </td>
  25. </tr>
  26. </tbody>
  27. </table>
  28. </div>
  29. </template>
  30. <script>
  31. export default {}
  32. </script>
  33. <style>
  34. </style>
  • 在组件初始化时,获取英雄列表数据
  1. // 导入axios
  2. import axios from 'axios'
  3. export default {
  4. data () {
  5. return {
  6. list: []
  7. }
  8. },
  9. created () {
  10. this.loadBookList()
  11. },
  12. methods: {
  13. // 获取英雄列表数据
  14. async loadBookList () {
  15. // 通过axios发查询列表请求
  16. const ret = await axios.get('http://localhost:3000/books')
  17. this.list = ret.data
  18. }
  19. }
  20. }
  • 根据list数据,进行模板渲染。
  1. <tbody> <tr :key='item.id' v-for='item in bookList'> <td>{{item.id}}</td> <td>{{item.bname}}</td> <td>{{item.author}}</td> <td>{{item.ctime}}</td> <td> <button class="btn btn-success">编辑</button> <button class="btn btn-danger">删除</button> </td> </tr></tbody>

总结:调用接口获取图书列表数据;动态渲染页面 注意:axios需要导入使用

添加图书组件

目标:实现添加图书功能

  1. <form>
  2. <legend>添加图书</legend>
  3. <div class="form-group">
  4. <label>图书名称</label>
  5. <input v-model='bname' type="text" class="form-control">
  6. </div>
  7. <div class="form-group">
  8. <label>图书作者</label>
  9. <input v-model='author' type="text" class="form-control">
  10. </div>
  11. <button @click='handleSubmit' type="button" class="btn btn-primary">提交</button>
  12. </form>
  • 控制添加表单页面的路由跳转
  1. <router-link to='/home/add' class="btn btn-primary">添加图书</router-link>
  1. const routes = [
  2. { path: '/', redirect: '/login' },
  3. { path: '/login', component: Login },
  4. {
  5. path: '/home',
  6. component: Home,
  7. redirect: '/home/books',
  8. children: [
  9. { path: 'books', component: BookList },
  10. { path: 'persons', component: PersonList },
  11. { path: 'floors', component: FloorList },
  12. + { path: 'add', component: BookAdd }
  13. ]
  14. }
  15. ]
  • 提交表单
  1. methods: { async handleSubmit () { // 调用接口添加图书 const ret = await axios.post('http://localhost:3000/books', { bname: this.bname, author: this.author, ctime: new Date() }) if (ret.status === 201) { // 添加图书成功,跳转到图书列表页面 this.$router.push('/home/books') } else { alert('添加图书失败') } } }

总结:点击跳转页面;准备布局绑定表单数据;绑定提交事件;调用接口添加;成功后跳转到列表页面。

编辑图书功能

目标:实现编辑英雄功能

  1. 根据图书id查询图书信息并显示到表单里
  • 控制页面跳转
  1. // 控制跳转到编辑图书页面handleJump (id) { // /home/edit/1 this.$router.push('/home/edit/' + id)},
  • 基于动态路由传递参数
  1. { path: 'edit/:id', component: BookEdit, props: true },
  • 根据id查询图书的信息
  1. import axios from 'axios'
  2. export default {
  3. props: ['id'],
  4. data () {
  5. return {
  6. book: {}
  7. }
  8. },
  9. methods: {
  10. // 根据id查询图书的信息
  11. async loadBookInfo () {
  12. const ret = await axios.get('http://localhost:3000/books/' + this.id)
  13. this.book = ret.data
  14. }
  15. },
  16. created () {
  17. this.loadBookInfo()
  18. }
  19. }
  • 把数据填充到表单里面
  1. <form>
  2. <legend>编辑图书</legend>
  3. <div class="form-group">
  4. <label>图书名称</label>
  5. <input v-model='book.bname' type="text" class="form-control">
  6. </div>
  7. <div class="form-group">
  8. <label>图书作者</label>
  9. <input v-model='book.author' type="text" class="form-control">
  10. </div>
  11. <button @click='handleSubmit' type="button" class="btn btn-primary">提交</button>
  12. </form>

总结:页面跳转;获取图书列表数据;填充表单。

  1. 编辑完成数据后提交表单
  • 绑定表单提交事件
  1. <button @click='handleSubmit' type="button" class="btn btn-primary">提交</button>
  • 实现提交功能
  1. // 提交表单事件函数
  2. async handleSubmit () {
  3. const ret = await axios.patch('http://localhost:3000/books/' + this.book.id, this.book)
  4. if (ret.status === 200) {
  5. // 更新成功,跳转到列表页面
  6. this.$router.push('/home/books')
  7. } else {
  8. alert('编辑失败')
  9. }
  10. }

总结:绑定表单提交事件,实现提交功能

删除图书功能

目标:实现删除英雄功能

  1. 绑定删除按钮点击事件
  2. 弹出确认框
  3. 点击确认,发送删除请求
  4. 删除成功,更新当前列表
  1. <button @click="handleDelete(item.id)" class="btn btn-danger">删除</button>
  1. // 处理删除操作async handleDelete (id) { if (confirm('确实要删除吗?')) { // 如果点击了确定,那么执行这个流程 const ret = await axios.delete('http://localhost:3000/books/' + id) if (ret.status === 200) { // 删除成功,刷新列表 this.loadBookList() } else { alert('删除失败') } }},

总结:绑定事件;调用接口;刷新列表

时间过滤器

目标:基于moment包实现时间格式化过滤器

  • 安装 moment
  1. npm i moment
  • 导入moment BookList.vue
  1. // 导入moment
  2. import moment from 'moment';
  • 定义过滤器
  1. filters: {
  2. // formatTime 过滤器名字,对应的函数处理格式。
  3. formatTime (value) {
  4. // 使用过滤器的时候,管道符 | 前的js表达式执行结果,就是value
  5. return moment(value).format('YYYY-MM-DD HH:mm:ss')
  6. }
  7. },
  • 使用过滤器
  1. <td>{{item.cTime|formatTime}}</td>

总结:

  1. 过滤器用法
  2. 第三方包moment的用法

axios全局配置

目标:全局配置axios

  1. // 进行axios的全局挂载
  2. import axios from 'axios'
  3. // 将来通过vue的实例访问$http,其实就是axios。
  4. Vue.prototype.$http = axios

总结:

  1. 面向对象:实例对象可以访问构造函数原型上的属性和方法
  2. axios可以统一配置基准路径
  3. 所有的组件都是Vue构造函数的实例对象