项目--3
1、回顾
2、token校验登录 ---- day06/myapp
cnpm i jsonwebtoken -S 安装 token 模块
2.1 当登录成功时 生成token,给前端返回token
var jwt = require('jsonwebtoken');
// 实现登陆功能
router.post('/login', (req, res, next) => {
// 1、获取表单信息
let { tel, password } = req.body;
// 2、依据手机号查询有没有该用户
sql.find(User, { tel }, { _id: 0 }).then(data => {
// 2.1 判断有么有该用户
if (data.length === 0) {
// 2.2 没有该用户
res.send(utils.unregister)
} else {
// 2.3 有该用户,验证密码
// 2.3.1 获取数据库中的密码
let pwd = data[0].password;
// 2.3.2 比较 输入的 密码和数据库中的密码
var flag = bcrypt.compareSync(password, pwd) // 前为输入,后为数据库
if (flag) {
// 2.3.3 密码正确,生成token
let userid = data[0].userid
let token = jwt.sign({ userid }, 'daxunxun', {
// expiresIn: 60*60*24// 授权时效24小时
expiresIn: 60*0.1// 授权时效6s
})
res.send({
code: '10010',
message: '登陆成功',
token: token
})
} else {
// 2.3.4 密码错误
res.send({
code: '10100',
message: '密码错误'
})
}
}
})
})
2.2 全局校验token,如果没有token信息,表示未登录需要重新登陆之后再操作 app.js
var jwt = require('jsonwebtoken');
app.use((req, res, next) => {
// 1、如果不是登陆和注册页面
if (req.url !== '/users/login' && req.url !== '/users/register') {
// 1.1 获取前端提交的token信息(get/post/头信息)
let token = req.headers.token || req.query.token || req.body.token;
if (token) {
// 1.2 如果存在token,校验token
jwt.verify(token, 'daxunxun', function(err, decoded) {
if (err) {
// 1.2.1 如果校验token失败,返回字段
res.send({
code: '10119',
message: '没有找到token.'
});
} else {
// 1.2.2 如果校验token成功,继续操作接口
req.decoded = decoded;
console.log('验证成功', decoded);
next()
}
})
} else {
// 1.3 如果没有传递token,返回字段
res.send({
code: '10119',
message: '没有找到token.'
});
}
} else {
// 2、如果是登陆和注册,继续操作
next()
}
})
3、登陆功能
3.1 创建登陆页面 views/login/index.vue
<template>
<div class="box">
<header class="header">登陆</header>
<div class="content">
<input type="text" placeholder="手机号码" v-model="tel">
<p class="tip">{{ teltip }}</p>
<input type="password" placeholder="密码" v-model="password">
<p class="tip">{{ passwordtip }}</p>
<button class="userBtn" @click="login">登陆</button>
</div>
</div>
</template>
<script>
export default {
data () {
return {
tel: '18813007814',
password: '123456'
}
},
computed: {
teltip () {
return '手机号码格式错误'
},
passwordtip () {
return '密码格式错误'
}
},
methods: {
login () {
}
}
}
</script>
<style lang="scss">
input {
outline: none;
border: 0;
width: 96%;
margin: 5px 2%;
border-bottom: 1px solid #efefef;
text-indent: 10px;
display: block;
line-height: 36px;
}
.tip {
text-align: center;
color: #f66;
height: 20px;
}
.userBtn {
outline: none;
border: 0;
display: block;
background-color:#f66;
width: 96%;
margin: 15px 2%;
line-height: 40px;
font-size: 18px;
color: #fff;
}
</style>
3.2 添加登陆的路由 router/index.js
{
path: '/login',
name: 'login',
components: { // 一个路由 对象两个位置发生变化
default: () => import('@/views/login/index.vue')
}
}
浏览器输入 /login 查看效果
3.3 检验手机号和密码
computed: {
teltip () {
if (this.tel === '') {
return ''
} else if (this.tel.length !== 11) {
return '手机号码格式错误'
} else {
return ''
}
},
passwordtip () {
if (this.password === '') {
return ''
} else if (this.password.length < 6) {
return '密码格式错误'
} else {
return ''
}
}
},
3.4 登陆
methods: {
login () {
if (this.tel === '' || this.teltip !== '') {
this.tip = '手机号格式错误'
return
}
if (this.password === '' || this.passwordtip !== '') {
this.tip = '密码格式错误'
return
}
// 登陆
axios.post('/users/login', {
tel: this.tel,
password: this.password
}).then(res => {
console.log(res.data)
/***
* 10086 未注册
* 10100 密码错误
* 10010 登陆成功
*/
if (res.data.code === '10086') {
this.tip = '该用户未注册,请先注册'
} else if (res.data.code === '10100') {
this.tip = '密码错误'
} else {
// 此时为登陆成功,获取token信息存入本地
this.tip = ''
const token = res.data.token
localStorage.setItem('token', token)
}
})
}
}
4、修改首页的请求、详情页面的请求,给每一个请求添加参数token
4.1 首页面
- 结构 ---- 登陆显示列表,未登录显示 登陆提示信息
<div class="content">
<!-- 使用组件 -->
<Prolist v-if="flag" :prolist="prolist"/>
<div v-else>
登陆之后才能看到更多的信息
<router-link to="/login">登陆</router-link>
</div>
</div>
- 行为
data () {
return {
prolist: [],
flag: false
}
},
created () {
axios.get('/pro?token=' + localStorage.getItem('token')).then(res => {
console.log(res.data)
if (res.data.code === '10119') {
this.flag = false
} else {
this.flag = true
this.prolist = res.data.data
}
})
}
4.2 详情页面
- 结构
<div class="content">
<div v-if="flag">
<img :src="proimg" alt="">
<h1>{{ proname }}</h1>
<h3>{{ note }}</h3>
<p>{{ price }}</p>
<ul>
<li v-for="item of commentlist" :key="item.commentid">
<h4>{{ item.username }} - {{ item.rating }}</h4>
<p>{{ item.note }}</p>
</li>
</ul>
</div>
<div v-else>
登陆之后才能看到更多的信息
<router-link to="/login">登陆</router-link>
</div>
</div>
- 行为
data () {
return {
flag: false,
proid: '',
proname: '',
proimg: '',
price: '',
note: '',
commentlist: []
}
},
created () {
console.log(this.$route.query)
const proid = this.$route.query.proid
axios.get('/pro/detail?proid=' + proid + '&token=' + localStorage.getItem('token')).then(res => {
console.log(res.data)
if (res.data.code === '10119') {
this.flag = false
} else {
this.flag = true
this.proid = res.data.data.proid
this.proname = res.data.data.proname
this.proimg = res.data.data.proimg
this.price = res.data.data.price
this.note = res.data.data.note
}
})
axios.get('/comment?proid=' + proid + '&token=' + localStorage.getItem('token')).then(res => {
// console.log(res.data.data)
if (res.data.code === '10119') {
this.flag = false
} else {
this.flag = true
this.commentlist = res.data.data
}
})
}
5、使用UI库
- PC element-ui iview
- 移动端 vant mint-ui
找到UI库API文档,安装模块,找到快速上手,按照步骤配置UI库,复制-粘贴-删除-修改
5.1 安装依赖
cnpm i vant -S
5.2 配置UI库 -- 按需引入
cnpm i babel-plugin-import -D
修改babel.config.js文件
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
],
// +++++++++++++++++
plugins: [
['import', {
libraryName: 'vant',
libraryDirectory: 'es',
style: true
}, 'vant']
]
}
配置文件的修改,一定要记得重新启动服务器
6、使用UI库的 轮播图 构建首页的轮播图
https://youzan.github.io/vant/#/zh-CN/swipe
- 获取轮播图如果不需要校验登陆状态,需要更改后台的代码 app.js中过滤掉
app.use((req, res, next) => {
if (req.url !== '/users/login' && req.url !== '/users/register' && req.url !== '/banner') {}
})
6.1 首页轮播图
- 首页引入 轮播图组件
import Vue from 'vue'
import { Swipe, SwipeItem } from 'vant'
Vue.use(Swipe).use(SwipeItem) // 使用该语句,组件就无需再单独注册
- 请求轮播图数据
data () {
return {
bannerlist: [], // *************
prolist: [],
flag: false
}
},
created () {
// ++++++++++++++++++++
axios.get('/banner').then(res => {
console.log(res.data)
this.bannerlist = res.data.data
})
axios.get('/pro?token=' + localStorage.getItem('token')).then(res => {
console.log(res.data)
if (res.data.code === '10119') {
this.flag = false
} else {
this.flag = true
this.prolist = res.data.data
}
})
}
- 渲染数据
<!-- 轮播图 -->
<van-swipe :autoplay="3000" indicator-color="white">
<van-swipe-item v-for="item of bannerlist" :key="item.bannerid">
<img :src="item.img" alt="">
</van-swipe-item>
</van-swipe>
- 依据需求修改相应的样式
7、商品详情页添加商品导航组件
https://youzan.github.io/vant/#/zh-CN/goods-action
-
引入组件
-
修改
8、地址选择
views/address/index.vue + 路由
{
path: '/address',
name: 'address',
components: { // 一个路由 对象两个位置发生变化
default: () => import('@/views/address/index.vue')
}
}
<template>
<div class="box">
<header class="header">城市选择</header>
<div class="content">
<van-index-bar>
<van-index-anchor v-for="(item, index) of list" :key="index" :index="item.letter" >
<van-cell v-for="itm of item.cities" :key="itm.id" :title="itm.name" />
</van-index-anchor>
</van-index-bar>
</div>
</div>
</template>
<script>
import Vue from 'vue'
import { IndexBar, IndexAnchor, Cell } from 'vant'
import axios from 'axios'
Vue.use(IndexBar).use(IndexAnchor)
Vue.use(Cell)
export default {
data () {
return {
list: []
}
},
created () {
axios.get('/city.json').then(res => {
console.log(res.data)
this.list = res.data
})
}
}
</script>
<style lang="scss" scoped>
.van-index-bar {
height: 100%;
overflow: auto;
}
</style>
9、上拉加载
https://youzan.github.io/vant/#/zh-CN/list
- 引入组件
import axios from 'axios'
import Vue from 'vue'
import { Swipe, SwipeItem, List } from 'vant' // ++++++
// 引入列表的组件 ---- es6中的模块化
import Prolist from '@/components/Prolist.vue'
Vue.use(Swipe).use(SwipeItem)
Vue.use(List) // ++++++
- List 组件通过loading和finished两个变量控制加载状态,当组件滚动到底部时,会触发load事件并将loading设置成true。此时可以发起异步操作并更新数据,数据更新完毕后,将loading设置成false即可。若数据已全部加载完毕,则直接将finished设置成true即可。
data () {
return {
bannerlist: [],
prolist: [],
flag: false,
// ++++++++
loading: false, // 表示当前是不是正在加载,如果为真,表示可以请求数据,请求成功置为false
// ++++++++
finished: false, // 为true表示所有数据都已加载完毕
// +++++++++
pageCode: 1 // 页码 从1开始,默认值为0
}
},
methods: {
onLoad () { // 页面触底 触发该函数,可以加载下一页的数据
this.loading = true // 开始加载数据
axios.get('/pro?limitNum=10&pageCode=' + this.pageCode + '&token=' + localStorage.getItem('token')).then(res => {
console.log(res.data)
this.loading = false // 表示加载结束
this.pageCode++ // 加载结束 页码加1
if (res.data.code === '10119') { // 未登录
this.flag = false
} else { // 可以拿到数据
this.flag = true
// 判断有没有数据,如果没有数据,告诉没有数据了,如果有数据,拼接数据
if (res.data.data.length === 0) {
this.finished = true // 表示数据已经加载完毕
} else {
// 拼接数据 ----- 数组的合并
// arr.concat(arr1)
// [...arr, ...arr1] es6中的合并数组
this.prolist = [...this.prolist, ...res.data.data]
}
}
})
}
}
- 结构中使用
<!-- 使用组件 -->
<van-list
v-model="loading"
:finished="finished"
finished-text="没有更多了"
@load="onLoad"
>
<Prolist v-if="flag" :prolist="prolist"/>
<div v-else>
登陆之后才能看到更多的信息
<router-link to="/login">登陆</router-link>
</div>
</van-list>
10 下拉刷新
https://youzan.github.io/vant/#/zh-CN/pull-refresh
- 引入组件
import axios from 'axios'
import Vue from 'vue'
import { Swipe, SwipeItem, List, PullRefresh } from 'vant' // ++++++
// 引入列表的组件 ---- es6中的模块化
import Prolist from '@/components/Prolist.vue'
Vue.use(Swipe).use(SwipeItem)
Vue.use(List)
Vue.use(PullRefresh) // ++++++
- js
data () {
return {
bannerlist: [],
prolist: [],
flag: false,
loading: false, // 表示当前是不是正在加载,如果为真,表示可以请求数据,请求成功置为false
finished: false, // 为true表示所有数据都已加载完毕
pageCode: 1,
// ++++++++++++++
isLoading: false // 在不在刷新,如果为真,可以请求数据,请求完毕设置为false
}
},
methods: {
onLoad () { // 页面触底 触发该函数,可以加载下一页的数据
this.loading = true // 开始加载数据
axios.get('/pro?limitNum=10&pageCode=' + this.pageCode + '&token=' + localStorage.getItem('token')).then(res => {
console.log(res.data)
this.loading = false // 表示加载结束
this.pageCode++ // 加载结束 页码加1
if (res.data.code === '10119') { // 未登录
this.flag = false
} else { // 可以拿到数据
this.flag = true
// 判断有没有数据,如果没有数据,告诉没有数据了,如果有数据,拼接数据
if (res.data.data.length === 0) {
this.finished = true // 表示数据已经加载完毕
} else {
// 拼接数据 ----- 数组的合并
// arr.concat(arr1)
// [...arr, ...arr1] es6中的合并数组
this.prolist = [...this.prolist, ...res.data.data]
}
}
})
},
// +++++++++++++++++++++++++
onRefresh () { // 下拉触发此函数
this.isLoading = true // 表示可以请求第一页(默认)的数据
axios.get('/pro?token=' + localStorage.getItem('token')).then(res => {
console.log(res.data)
this.isLoading = false // 表示下拉刷新请求函数结束
if (res.data.code === '10119') {
this.flag = false
} else {
this.flag = true
this.finished = false // 表示还可以继续上拉加载
this.pageCode = 1 // 下拉刷新即加载第一页数据,刷新之后重置页码
this.prolist = res.data.data // 下拉刷新就是直接替换列表
}
})
}
}
- 结构 使用下拉刷新组件全部包含 content内容区域
<div class="content">
<van-pull-refresh v-model="isLoading" @refresh="onRefresh">
<!-- 轮播图 -->
<van-swipe :autoplay="3000" indicator-color="white">
<van-swipe-item v-for="item of bannerlist" :key="item.bannerid">
<img :src="item.img" alt="">
</van-swipe-item>
</van-swipe>
<!-- 使用组件 -->
<van-list
v-model="loading"
:finished="finished"
finished-text="没有更多了"
@load="onLoad"
>
<Prolist v-if="flag" :prolist="prolist"/>
<div v-else>
登陆之后才能看到更多的信息
<router-link to="/login">登陆</router-link>
</div>
</van-list>
</van-pull-refresh>
</div>
11、回到顶部功能
谁有滚动条,谁的scrollTop为0,跟布局相关
弹性盒布局 -- content -- 产生滚动条
11.1 准备小图标,更新public/index.html的css的值
//at.alicdn.com/t/font_1476238_uph8zgimp3.css
11.2 固定定位返回顶部小图标
<span class="backtop iconfont icon-fanhuidingbu"></span>
.backtop {
position: fixed;
bottom: 60px;
right: 10px;
font-size: 30px;
}
11.3 添加点击返回顶部事件
<div class="content" id="content"></div>
<span @click="backtop" class="backtop iconfont icon-fanhuidingbu"></span>
backtop () {
document.getElementById('content').scrollTop = 0
}
11.4 滚动到一定距离才出现返回顶部按钮
<span @click="backtop" v-show="topflag" class="backtop iconfont icon-fanhuidingbu"></span>
data () {
return {
bannerlist: [],
prolist: [],
flag: false,
loading: false, // 表示当前是不是正在加载,如果为真,表示可以请求数据,请求成功置为false
finished: false, // 为true表示所有数据都已加载完毕
pageCode: 1,
isLoading: false, // 在不在刷新,如果为真,可以请求数据,请求完毕设置为false
// +++++++++++++
topflag: false // 默认不显示返回顶部图标
}
},
watch: {
pageCode (newval, oldval) {
if (newval > 2) { // 因为刚开始会默认加载一次数据,加载完之后页码已为2
this.topflag = true
} else {
this.topflag = false
}
}
},
methods: {
backtop () {
document.getElementById('content').scrollTop = 0
this.topflag = false // ++++++++++++++++++++
}
}
长风破浪会有时,直挂云帆济沧海