一、登录页
课程目标
- 复习vue脚手架
- 搭建vue大型项目框架
- 制作一级路由页面登录页面
知识点
1.v-model
登录页有两个输入框,分别是用户名和密码。用户在输入框里输入的内容可以通过v-model指令和data进行双向数据绑定。
可以带着学生回忆一下在没有vue的时候怎么做这个功能。在react里没有v-model指令,是通过受控组件来做的,两者有什么
区别?能否在vue里用受控组件代替v-model?
语法格式:
<input v-model="username">
2.axios
前后端通信的工具,如何安装axios?添加登录按钮,并绑定登录事件,在登录回调函数里如何获取输入框里的值?
获取到值后,使用axios把用户名和密码发送给后端。
语法格式:
<button @click="handleLogin">登录</button>
methods: {
handleLogin() {
Api.login({ username: this.username, password: this.password }).then(res => {
})
}
}
3.如何写后端接口
可以在vue.config.js里写,也可以利用在专高一时所学的知识,搭建后端项目,使用express写接口。
vue.config.js是vue脚手架的api,会随着项目的启动自动加载。建议安装body-parser解析post请求。
语法格式:
const bodyParser = require('body-parser')
let userList = [
{
id: 0,
username: 'admin',
password: '123456'
},
{
id: 1,
username: 'xu',
password: '123'
}
]
module.exports = {
lintOnSave: false,
devServer: {
open: true,
before(app) {
app.use(bodyParser.json())
app.post('/api/login', (req, res) => {
let { username, password } = req.body
let user = userList.find(item => item.username === username)
if (user) {
if (user.password === password) {
res.send({
code: 200,
data: {
username
},
message: '登录成功'
})
} else {
res.send({
code: 400,
message: '密码错误'
})
}
} else {
res.send({
code: 400,
message: '用户名不存在'
})
}
})
}
}
}
4.登录页面的优化
首页要做的是用户名输入框自动获取焦点,可以给学生看看百度首页,每次刷新时百度首页的搜索框都会自动获取焦点,这样
做对用户来说使用起来很方便!
语法格式:
<input v-model="username" placeholder="请输入用户名" autofocus >
还有一个需要优化的是回车登录。这个也可以举百度的例子,就是用户输入完成后,点击回车键即可搜索!
在vue里可以使用.enter修饰keyup事件做到这个功能。
语法格式:
<input @keyup.enter="handleLogin" >
授课思路
案例作业
1.练习v-model
2.制作登录页
3.前后端通信
4.可以包含多个用户
5.区分用户名错误和密码错误
二、登录页控制密码显示隐藏
课程目标
- 复习登录页主要知识点
- 复习vue组件化的思想
- 讲解父子组件传值
- 讲解iconfont的用法以及为什么要用iconfont
- 封装Icon组件,使用iconfont,控制密码输入框的密码显示和隐藏
知识点
1.组件化思想
组件化就是把传统的一张大网页拆分成很多小组件,有些组件可以反复使用,有些组件也许只使用一次,拆分成很多组件的目的就是使页面解构更清晰更好维护,也方便多人协作开发一个大项目。
2.vue组件
首先带学生回忆一下react里如何创建组件,然后再用快捷方式创建一个vue的组件。讲解vue组件的模板、js以及样式这三大模块在react组件里是如何实现的,还可以讲解一下jsx多么的方便,而vue又哪些弊端。再给学生展示一下vue和react在github上star数对比以及npm下载量对比。
3.iconfont
讲解iconfont和雪碧图的区别。现场演示登录iconfont网站,并添加眼睛睁开和眼睛闭上的图标,并下载到本地。打开demo页,调试网页,
介绍iconfont的原理。
4.制作Icon组件
引入iconfont文件,编写Icon组件。
重点内容:props的类型检查,如何动态绑定class,子组件触发父组件的方法
语法格式:
<template>
<span :class="[ `icon iconfont icon-${name}` ]" @click="handleClick"></span>
</template>
<script>
export default {
props: {
name: String
},
methods: {
handleClick() {
this.$emit('onClick')
}
}
}
</script>
5.在登录页使用Icon组件
如果引入组件并使用
语法格式:
<template>
<div>
<div>
<input v-model="username" placeholder="请输入用户名" autofocus >
</div>
<div>
<input v-model="password" placeholder="请输入密码" :type=" visible ? 'text' : 'password'" @keyup.enter="handleLogin" >
<Icon :name="visible ? 'xianshimima' : 'buxianshimima'" class="m-login-icon" @onClick="handleVisible" ></Icon>
</div>
<button @click="handleLogin">登录</button>
</div>
</template>
<script>
import axios from 'axios'
import Icon from '../components/Icon'
import Api from '../api'
export default {
data() {
return {
username: 'admin',
password: '123456',
visible: false
}
},
components: {
Icon
},
methods: {
handleLogin() {
Api.login({ username: this.username, password: this.password }).then(res => {
if (res.code === 200) {
localStorage.setItem('token', res.data.username)
this.$router.push('/index/home')
} else {
this.$message({ message: res.message, duration: 1000 })
}
})
},
handleVisible() {
this.visible = !this.visible
}
}
}
</script>
<style>
</style>
授课思路
案例作业
1.完善登录页
2.研究iconfont的用法
3.编写vue组件
4.控制密码的显示和隐藏
三、封装Api
课程目标
- 讲解为什么要封装Api
- 首先封装url
- 其次要复习复习Promise
- 讲解拦截器
- 讲解封装本身代码
知识点
1.为啥要封装Api呢
为了好维护,减少代码量!把前后端通信的url单独写的一个文件里,有多少接口就一目了然了。把所有前端发起请求的方法也写到一个文件里,方法名抛出,可以减少代码量。另外还可以给axios添加拦截器,对报错信息统一处理,统一添加请求头等等。
2.首先封装url
封装url并没有用到啥新技术,js模块化而已!把url归归类,也可以写写注释,甚至还可以继续拆分成多个文件,多人协同维护。
语法格式:
const urls = {
login: '/api/login',
list: '/api/list',
myBooks: '/api/my_books'
}
export default urls
3.复习Promise
axios请求本身就是一个promise,如果想对axios进行二次封装,要好好研究一下。promise对象是可以通过.then处理成功的回调的,在封装之前大家一直也都这么用,封装之后也需要继续保持。async函数本身也是一个promise,且async函数内部可以通过await等待异步函数执行。
4.拦截器
拦截器只是一个概念,其实很好理解。可以类比之前讲的express中间件,其实二者的语法格式都是一致的。重点不是语法,重点是拦截器能帮咱干啥。可以类比高速公路的上高速前领一张卡和下高速前把卡交回并缴费。回到拦截器里就是请求前可以在header里添加token等信息,请求后可以帮忙这一些消息框的提示拦截。
5.封装axios
这块是核心代码,会有点多,核心思想就是把请求的核心部分封装成一个函数,抛出多个函数对应多个接口。
语法格式:
import axios from 'axios'
import message from '../components/message'
import urls from './urls'
if (process.env.NODE_ENV === 'development') {
axios.defaults.baseURL = 'http://localhost:81'
}
axios.interceptors.request.use((config) => {
config.headers.token = localStorage.getItem('token')
return config
})
axios.interceptors.response.use((res) => {
if (res.data.code === 400) {
message({ message: res.data.message, duration: 2000 })
}
return res
})
const common = async (config) => {
let resolve = await axios(config)
return resolve.data
}
const Api = {
login: (data) => common({ url: urls.login, data, method: 'post' }),
list: () => common({ url: urls.list }),
myBooks: (data, method) => common({ url: urls.myBooks, data, method })
}
export default Api
授课思路
案例作业
1.登录页调用封装后的api
2.在后端添加对一个数组的增删查改的接口
3.前端封装对应的接口并调用
4.研究get请求和post请求的区别
四、二级路由
课程目标
- 登录后跳转到新页面
- 新页面是个三段式
- header、footer拆分成组件
- 中间部分是二级路由,动态切换
知识点
1.如何跳转路由
如果用户名和密码都是正确的,后端会返回登录成功,前端就需要跳转页面了。可以给学生扩展一下路由的原理,history模式和hash模式到底啥区别?
语法格式:
this.$router.push('/index/home')
2.三段式
盒子需要覆盖整个浏览器,flex纵向布局。头和尾高度固定,中间部分flex:1
.m-wrap{display: flex;flex-direction: column;position: absolute;top: 0;left: 0;right: 0;bottom: 0;}
.m-header{height: 40px;line-height: 40px;background: red;color: #fff;text-align: center;}
.m-main{flex: 1;overflow-y: auto;}
.m-footer{display: flex; height: 40px;box-shadow: 0 -5px 5px rgba(0,0,0,0.1);border-top: 1px solid #ddd; text-align: center;z-index: 9;}
3.拆分组件
组件化依然是主体,header和footer两个组件。这个两个组件还是有很多文章可以做的,可以简单讲一下先不展开,先把二级路由讲清楚!
<template>
<div class="m-wrap">
<Header></Header>
<router-view></router-view>
<Footer></Footer>
</div>
</template>
4.二级路由
所谓二级,就是普通的页面里包含小页面。其实多少级都是一样的,学会二级就都学会了。router-view是页面渲染的地方,是可以嵌套。路由配置是数组对象,也可以通过children字段进行嵌套,和router-view形成对应。
语法格式:
const routes = [
{
path: '/index',
component: Index,
children: [{
path: '/index/home',
component: () => import('../views/Home')
}]
}
]
授课思路
案例作业
1.登录成功后跳转新页面
2.新页面是三段式,头和尾是组件,中间是二级路由
3.研究路由懒加载
4.预习vuex
五、引入VueX
课程目标
- 通过案例引入VueX
- 让学生掌握VueX的基本语法
- 了解路由守卫
知识点
1.VueX
VueX是难点、重点。可以多举一些有趣的例子加深学生理解。仓库好比是杂货铺,又好比是手机里的应用商店等等。还可以带着学生安装一下VueX在chrome里的插件,这样学习起来更直观些。这里把Vuex的最精简、最易上手的语法展示一下,学会这些就足够了,不必全面学习官方Api。
语法格式:
import Vue from 'vue'
import Vuex from 'vuex'
import Api from '../api'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
title: '小米书城'
},
mutations: {
setState(state, payload) {
state[payload.key] = payload.value
}
},
actions: {
getList({ commit }) {
Api.list().then(res => {
if (res.code === 200) {
commit({ type: 'setState', key: 'list', value: res.data })
}
})
}
}
})
title是仓库里的值,mutations里的setState方法负责修改仓库里的值,actions只是一个写异步函数的地方。
2.在Header里获取仓库里的值
通过计算属性获取,语法很简单,获取之后的使用也和data里的数据完全一样。当然也可以使用辅助函数获取,不过我不喜欢使用。
<template>
<div class="m-header">
{{title}}
</div>
</template>
<script>
export default {
computed: {
title() {
return this.$store.state.title
}
}
}
</script>
3.完善Footer组件
Footer组件包含首页、书包和我的三个菜单,除了文字外,应该添加上小图标,这样会更加有趣!之前的课程中已经封装了Icon组件在密码输入框里使用,这里是第二次是否Icon组件,Icon组件还会再接下来的课程中继续使用。
<template>
<div class="m-footer">
<router-link to="/index/home" class="m-footer-item">
<Icon name="shouye" class="m-footer-icon"></Icon>
<div class="m-footer-text">首页</div>
</router-link>
<router-link to="/index/my_books" class="m-footer-item">
<Icon name="shubao" class="m-footer-icon"></Icon>
<div class="m-footer-text">书包</div>
</router-link>
<router-link to="/index/me" class="m-footer-item">
<Icon name="wodedangxuan" class="m-footer-icon"></Icon>
<div class="m-footer-text">我的</div>
</router-link>
</div>
</template>
4.点击底部菜单动态修改Header里的文本
如果直接在底部菜单上面绑定click事件,点击的时候修改仓库里的title值,确实可以实现这个需求,但是刷新后会出bug,你可以试一试!原因很简单,store里的数据刷新后就回到初始值了。这里推荐一个新的解决方案,通过路由守卫实现!
语法格式:
children: [{
path: '/index/home',
component: () => import('../views/Home'),
meta: {
title: '小米书城'
}
}, {
path: '/index/my_books',
component: () => import('../views/MyBooks'),
meta: {
title: '我的书包'
}
}, {
path: '/index/me',
component: () => import('../views/Me'),
meta: {
title: '个人中心'
}
}]
router.beforeEach((to, from, next) => {
store.commit({ type: 'setState', key: 'title', value: to.meta.title })
next()
})
授课思路
案例作业
1.学习VueX语法
2.实现底部菜单切换顶部Header文本
3.学习Icon组件
4.学习actions
六、初识楼层
课程目标
- 深入掌握VueX
- 深入掌握组件化思想
- 搭建楼层页面框架
知识点
1.VueX之actions
actions是写异步函数的地方,通过异步请求获取数据。函数的第一个参数是context,context里包含state、commit等。通过commit把获取的数据提交给mutations,然后更新state。这是VueX给咱们设计出来的一套语法格式。
import Vue from 'vue'
import Vuex from 'vuex'
import Api from '../api'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
title: '小米书城',
list: [],
},
mutations: {
setState(state, payload) {
state[payload.key] = payload.value
}
},
actions: {
getList({ commit }) {
Api.list().then(res => {
if (res.code === 200) {
commit({ type: 'setState', key: 'list', value: res.data })
}
})
}
}
})
actions里的函数并不会自动触发,可以在组件挂载后这个生命周期里手动触发。
mounted() {
this.$store.dispatch({ type: 'getList' })
}
2.初始楼层页面
通过计算属性可以获取仓库里的数据。无论是哪个组件,都可以随时随地的获得仓库里的数据。我们在首页里使用两个组件,一个是侧边栏,另一个是列表。点击侧边栏的菜单可以控制列表滚动到对应的数据位置,同时右侧数据滚动时,侧边栏菜单的高亮也会有所变化,其实就是楼层的效果!
这是首页的代码,可以清醒的看到使用了侧边栏组件和列表组件,以及在挂载完这个生命周期派发了getList这个action去请求数据
<template>
<div class="m-main m-home">
<Sidebar></Sidebar>
<List></List>
</div>
</template>
<script>
import Sidebar from '../components/Sidebar'
import List from '../components/List'
export default {
components: {
Sidebar,
List
},
mounted() {
this.$store.dispatch({ type: 'getList' })
}
}
</script>
3.侧边栏组件
侧边栏里有菜单,菜单的数据需要从仓库里获得,仓库里的list数据是一个数组。
我们需要使用v-for把数组里的第一层的title字段渲染成菜单,并给每一个菜单菜单绑定点击事件,通过点击事件控制仓库里的currentId,该字段和菜单的高亮有关。
<template>
<div class="m-sidebar">
<div
v-for="item in list"
:key="item.id"
class="m-sidebar-item"
:class="currentId === item.id ? 'active' : ''"
@click="handleNav(item.id)">{{item.title}}</div>
</div>
</template>
<script>
let timer
export default {
computed: {
list() {
return this.$store.state.list
},
currentId() {
return this.$store.state.currentId
}
},
methods: {
handleNav(id) {
this.$store.commit({ type: 'setState', key: 'currentId', value: id })
}
}
}
</script>
4.List组件
List组件这节课并不讲太多内容,先渲染一下列表即可!渲染的时候需要注意一下这是一个两层的数组,第一层是分类,第二层是分类对应的列表。
<template>
<div class="m-list">
<div v-for="categroy in list" :key="categroy.id" :id="categroy.id" class="m-category js-category">
<div class="m-category-title">{{categroy.title}}</div>
<div v-for="book in categroy.list" :key="book.id" class="m-list-item">
<div class="m-info">
{{book.title}}
</div>
</div>
</div>
</div>
</template>
<script>
export default {
computed: {
list() {
return this.$store.state.list
}
},
}
</script>
授课思路
案例作业
1.编辑actions函数并把返回的数据保存到仓库
2.搭建楼层页面框架
3.开发侧边栏组件并添加高亮
4.开发列表组件