项目--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库,复制-粘贴-删除-修改

以vant为例: https://youzan.github.io/vant/#/zh-CN/intro

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 // ++++++++++++++++++++
  }
}
posted @ 2019-11-01 14:04  菜鸟小何  阅读(251)  评论(0编辑  收藏  举报