项目--4

1、回顾

2、调整首页的布局

<van-list
  v-if="flag"
  v-model="loading"
  :finished="finished"
  finished-text="没有更多了"
  @load="onLoad"
>
  <Prolist :prolist="prolist"/>
</van-list>
<div v-else>
  登陆之后才能看到更多的信息
  <router-link to="/login">登陆</router-link>
</div>

3、加入购物车的功能

接口: /cart/add?userid=1&proid=2&num=1&token=111

3.1 修改登陆接口,登陆成功返回加字段 userid 和 username,前端保存到本地

  • day06/myapp/routes/users.js
// 实现登陆功能
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 username = data[0].username // +++++++++++++++++
        let token = jwt.sign({ userid }, 'daxunxun', {
          // expiresIn: 60*60*24// 授权时效24小时
          expiresIn: 60*60*24*7// 授权时效7天
        })
        res.send({
          code: '10010',
          message: '登陆成功',
          token: token,
          userid, // ++++++++++++++++++++++++++++++++++++++++++++++++++++++
          username // +++++++++++++++++++++++++++++++++++++++++++++++++++++
        })
      } else {
        // 2.3.4 密码错误
        res.send({
          code: '10100',
          message: '密码错误'
        })
      }
    }
  })
})
  • 登陆页面将userid保存到本地

day09/src/views/login/index.vue

methods: {
    login () {
      console.log('11111')
      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)
          localStorage.setItem('userid', res.data.userid) // +++++++++++
          localStorage.setItem('username', res.data.username) // +++++++++++++
          this.$router.back()
        }
      })
    }
  }

3.2 详情页面点击加入购物车 执行加入购物车逻辑

<van-goods-action-icon icon="cart-o" @click="toCart" text="购物车"  />
<van-goods-action-button type="warning" @click="addCart" text="加入购物车" />

methods: {
  addCart () {
    let userid = localStorage.getItem('userid')
    let token = localStorage.getItem('token')
    let proid = this.proid
    let num = 1
    let url = '/cart/add?userid=' + userid + '&proid=' + proid + '&num=' + num + '&token=' + token
    axios.get(url).then(res => {
      // 如果未登录,跳转到登陆
      if (res.data.code === '10119') {
        this.$router.push('/login')
      } else {
        Toast('加入购物车成功')
      }
    })
  },
  toCart () {
    this.$router.push('/cart')
  }
}

4、查看购物车

<template>
  <div class="box">
    <header class="header">购物车头部</header>
    <div class="content">
      <ul class="prolist" v-if="flag">
        <li class="proitem" v-for="(item, index) of cartlist" :key="item.proid" @click="toDetail(item.proid)">
          <div class="itemimg">
            <img :src="item.proimg" alt="">
          </div>
          <div class="iteminfo">
            <h2>{{ index }}-{{ item.proname }}</h2>
            <h3>{{ item.brand }}</h3>
            <p>{{ '¥' + item.price }}</p>
            <div>
              <button>-</button>{{ item.num }}<button>+</button>
            </div>
          </div>
        </li>
      </ul>
      <div v-else>
        购物车空空如也,<router-link to="/home">去购物</router-link>
      </div>
    </div>
  </div>
</template>

<script>
import axios from 'axios'
export default {
  data () {
    return {
      cartlist: [],
      flag: false // false 表示没有数据
    }
  },
  created () {
    let userid = localStorage.getItem('userid')
    let token = localStorage.getItem('token')
    let url = '/cart?userid=' + userid + '&token=' + token
    axios.get(url).then(res => {
      // 10119 未登录
      // 11000 没有数据
      // 有数据
      if (res.data.code === '10119') {
        this.$router.push('/login')
      } else if (res.data.code === '11000') {
        this.flag = false
      } else {
        this.flag = true
        this.cartlist = res.data.data
      }
    })
  }
}
</script>
<style lang="scss">
@import '@/lib/reset.scss';
.prolist {
  @include rect(100%, auto);
  .proitem {
    @include rect(100%, 1rem);
    @include border(0 0 1px 0, #efefef, solid); // 设定的是一个物理像素
    @include flexbox();
    .itemimg {
      @include rect(1rem, 1rem);
      img {
        @include rect(0.9rem, 0.9rem);
        @include border(1px, #f66, solid);
        @include margin(0.05rem);
        @include display(block);
      }
    }
    .iteminfo {
      @include flex();
    }
  }
}
</style>

5、购物车的数量加减、删除、选择

<div>
  <button @click="reduce(item)">-</button>{{ item.num }}<button @click="add(item)">+</button>
  <button @click="deleteItem(item, index)"> 删除 </button>
</div>

methods: {
  // 不管加还是减,记住传递参数为 对象
  reduce (item) {
    let token = localStorage.getItem('token')
    // 如果数量为1,不可以再减
    let num = item.num > 1 ? --item.num : 1
    // 请求数据库更新
    axios.get('/cart/update?token=' + token + '&cartid=' + item.cartid + '&num=' + num).then(res => {
      // 未登录
      if (res.data.code === '10119') {
        this.$router.push('/login')
      } else {
        // 更新成功  ---  更新对象的属性值-- 可以引起视图的二次渲染 -- 为什么传对象
        item.num = num
      }
    })
  },
  add (item) {
    let token = localStorage.getItem('token')
    let num = ++item.num
    console.log(num)
    axios.get('/cart/update?token=' + token + '&cartid=' + item.cartid + '&num=' + num).then(res => {
      console.log('cart', res.data)
      if (res.data.code === '10119') {
        this.$router.push('/login')
      } else {
        item.num = num
      }
    })
  },
  // 删除 需要传递对象和索引值  --- 目的是 删哪个 截取那个数据
  deleteItem (item, index) {
    let token = localStorage.getItem('token')
    axios.get('/cart/delete?token=' + token + '&userid=' + item.userid + '&proid=' + item.proid).then(res => {
      if (res.data.code === '10119') {
        this.$router.push('/login')
      } else {
        // 删除成功  --- 调整视图
        this.cartlist.splice(index, 1)
      }
    })
  }
}

6、计算总价以及总数量

  • 布局
<div class="submitOrder">
  <ul>
    <li>
      <p>
        总数: <span>11</span>
      </p>
    </li>
    <li>
      <p>
        合计: <span></span>
      </p>
    </li>
    <li>
      提交订单
    </li>
  </ul>
</div>

.submitOrder {
  @include rect(100%, 0.5rem);
  @include border(1px 0 0 0, #f66, solid);
  @include fixed();
  @include bottom(0.5rem);
  @include background-color(#fff);
  ul {
    @include rect(100%, 100%);
    @include flexbox();
    li {
      @include flexbox();
      @include justify-content();
      @include align-items();
      &:nth-child(1) {
        @include flex(4);
      }
      &:nth-child(2) {
        @include flex(4);
      }
      &:nth-child(3) {
        @include flex(2);
        @include background-color(#f66);
        @include color(#fff);
      }
      p {
        span{
          @include color(#f66);
        }
      }
    }
  }
}
  • 使用计算属性获取总价格总数量
<li>
  <p>
    总数: <span>{{ totalNum }}</span>
  </p>
</li>
<li>
  <p>
    合计: <span>{{ totalPrice }}</span>
  </p>
</li>

computed: {
  totalNum () {
    let num = 0
    this.cartlist.map((item) => {
      num += item.num
    })
    return num
  },
  totalPrice () {
    let totalPrice = 0
    this.cartlist.map((item) => {
      totalPrice += item.num * item.price
    })
    return totalPrice.toFixed(2)
  }
},

7、添加选择和全选

  • 给每一个列表前添加一个checkbox,底部添加一个checkbox --- cart/index.1.vue

  • 修改购物车列表,给每一条数据添加一个字段 flag,设置其值为true

created () {
  let userid = localStorage.getItem('userid')
  let token = localStorage.getItem('token')
  let url = '/cart?userid=' + userid + '&token=' + token
  axios.get(url).then(res => {
    // 10119 未登录
    // 11000 没有数据
    // 有数据
    if (res.data.code === '10119') {
      this.$router.push('/login')
    } else if (res.data.code === '11000') {
      this.flag = false
    } else {
      this.flag = true
      // ++++++++++++++++++++++++++++++
      let arr = res.data.data
      arr.map(item => { // 处理数据。每一项添加一个字段
        item.flag = true
      })
      this.cartlist = arr
      // ++++++++++++++++++++++++++++
    }
  })
},
  • 选中计算总数和总价
computed: {
  totalNum () {
    let num = 0
    this.cartlist.map((item) => {
      item.flag ? num += item.num : num += 0
    })
    return num
  },
  totalPrice () {
    let totalPrice = 0
    this.cartlist.map((item) => {
      item.flag ? totalPrice += item.num * item.price : totalPrice += 0
    })
    return totalPrice.toFixed(2)
  }
},
  • 全选 --- 点击全选 控制列表的选择 ----- 侦听属性 -------- 废弃
<input type="checkbox" v-model="all">全选

data () {
  return {
    cartlist: [],
    flag: false, // false 表示没有数据
    all: true // ++++++++++++++++++++++++++++++++
  }
},
watch: {
  all (newval) {
    if (newval) {
      this.cartlist.map(item => {
        item.flag = true
      })
    } else {
      this.cartlist.map(item => {
        item.flag = false
      })
    }
  }
},
  • 点击列表的选择框 改变 全选
// 添加点击事件
<input type="checkbox" v-model="item.flag" @change="changeFlag(item)">

changeFlag (item) {
  console.log(item)
  if (item.flag) { // 如果此值为真 --- 该项是被选中的,判断别的项是不是选中
    // 如果b为真,表示所有的都被选中
    // arr.every() 所有的条件为真结果才为真
    let b = this.cartlist.every(item => {
      return item.flag === true
    })
    if (b) { // 全部被选中
      this.all = true
    } else {
      this.all = false
    }
  } else { // 有某一项未被选中,肯定全部不被选中
    this.all = false
  }
}
  • 废弃了侦听属性改变,通过事件 点击全选改变列表
// 全选添加事件
<input type="checkbox" v-model="all" @change="selectAll">全选

selectAll () {
  if (this.all) {
    this.cartlist.map(item => {
      item.flag = true
    })
  } else {
    this.cartlist.map(item => {
      item.flag = false
    })
  }
}
  • 原则上购物车业务逻辑已经完毕结束,但是实际上环没有

8、将购物车选中的商品提交到确认订单页面

  • 1、点击提交订单是,获取选中的 商品(包含商品的id, 商品的数量,外加用户的id),提交这些信息到 相关的订单接口
  • 2、将选中的购物车的列表的相对应的 购物车的数据(以cartid),将此数据复制到订单的数据库,并且删除购物车中的该项数据库

9、分类

9.1 编写相关接口

day06/myapp/routes/pro.js

// 获取分类类型对应的品牌
router.get('/category', (req, res, next) => {
  let { type } = req.query
  sql.find(Pro, { type }, {_id: 0, brand:1, barndimg: 1}).then(data => {
    // 数组去重 https://www.cnblogs.com/le220/p/9130656.html
    let obj = {}
    // 利用reduce方法遍历数组,reduce第一个参数是遍历需要执行的函数,第二个参数是item的初始值
    data = data.reduce((item, next) => {
      obj[next.brand] ? '' : obj[next.brand] = true && item.push(next)
      return item
    }, [])
    res.send({
      code: '200',
      message: '获取分类类型列表',
      data: data
    })
  })
})

// 获取品牌类型对应的产品
router.get('/brandcategory', (req, res, next) => {
  let { brand } = req.query
  sql.find(Pro, { brand: brand }, {_id: 0}).then(data => {
    
    res.send({
      code: '200',
      message: '获取品牌分类列表',
      data: data
    })
  })
})

// 搜索
router.get('/search', (req, res, next) => {
  let { text } = req.query
  sql.find(Pro, { proname: eval('/' + text + '/') }, {_id: 0}).then(data => {
    
    res.send({
      code: '200',
      message: '搜索列表',
      data: data
    })
  })
})

9.2 编写分类页面结构

<template>
  <div class="box">
    <header class="header">分类头部</header>
    <div class="content">
      <div class="kind">
        <div class="left">
          <ul>
            <li>手机</li>
            <li>安装</li>
          </ul>
        </div>
        <div class="right"></div>
      </div>
    </div>
  </div>
</template>

<style lang="scss">
@import '@/lib/reset.scss';
.content {
  .kind {
    @include rect(100%, 100%);
    @include flexbox();
    .left {
      @include rect(1rem, 100%);
      @include background-color(#00f);
      ul {
        @include rect(100%, 100%);
        li{
          @include rect(100%, 0.36rem);
          @include border(0 0 1px, #efefef, solid);
          @include line-height(0.36rem);
          @include text-align();
        }
      }
    }
    .right {
      @include flex();
      @include rect(auto, 100%);
      @include background-color(#0f0);
      @include overflow();
    }
  }
}
</style>

9.3 获取左侧的列表数据并且渲染

<div class="left">
  <ul>
    <li v-for="(item, index) of kindlist" :key="index">{{ item }}</li>
  </ul>
</div>

data () {
  return {
    kindlist: []
  }
},
created () {
  let token = localStorage.getItem('token')
  let url = '/pro/type?type=type&token=' + token
  axios.get(url).then(res => {
    console.log(res.data)
    if (res.data.code === '10119') {
      this.$router.push('/login')
    } else {
      this.kindlist = res.data.data
    }
  })
}

9.4 点击左侧的类型获取品牌数据并且渲染

// @click="getBrand(item)"
<ul>
  <li v-for="(item, index) of kindlist" @click="getBrand(item)" :key="index">{{ item }}</li>
</ul>

data () {
  return {
    kindlist: [],
    brandlist: []
  }
},
methods: {
  getBrand (item) {
    let token = localStorage.getItem('token')
    let url = '/pro/category?token=' + token + '&type=' + item
    // 接口为新增接口
    axios.get(url).then((res) => {
      console.log(res.data)
      if (res.data.code === '10119') {
        this.$router.push('/login')
      } else {
        this.brandlist = res.data.data
      }
    })
  }
}

<div class="right">
  <div class="top">
    <ul>
      <li v-for="(item, index) of brandlist" :key = "index">
        <!-- <img :src="item.barndimg" alt=""> -->
        {{ item.brand }}
      </li>
    </ul>
  </div>
</div>

9.5 点击品牌获取列表的数据并且渲染

引入列表组件,组件传值即可

// @click="getlist(item)
<div class="top">
  <ul>
    <li v-for="(item, index) of brandlist" :key = "index" @click="getlist(item)">
      <!-- <img :src="item.barndimg" alt=""> -->
      {{ item.brand }}
    </li>
  </ul>
</div>

// +++++++++++++++
components: {
  Prolist
},
data () {
  return {
    kindlist: [],
    brandlist: [],
    prolist: [] // ++++++++++++++++++
  }
},
methods: {
  // 注意对应的接口
  getlist (item) {
    let token = localStorage.getItem('token')
    let url = '/pro/brandcategory?token=' + token + '&brand=' + item.brand
    axios.get(url).then((res) => {
      console.log(res.data)
      if (res.data.code === '10119') {
        this.$router.push('/login')
      } else {
        this.prolist = res.data.data
      }
    })
  }
}

9.6 添加分类的默认值

  • 左侧默认以及点击样式
// :class="kindindex === index ? 'active' : ''"
// @click="getBrand(item, index)"
<li :class="kindindex === index ? 'active' : ''" v-for="(item, index) of kindlist" @click="getBrand(item, index)" :key="index">{{ item }}</li>
data () {
  return {
    kindlist: [],
    brandlist: [],
    prolist: [],
    kindindex: 0 // ++++++++++++++
  }
},
methods: {
  getBrand (item, index) {
    this.kindindex = index
  }
}
  • 右侧默认以及点击样式
// @click="getlist(item, index)"
// :class="brandindex === index ? 'active' : ''"
<li :class="brandindex === index ? 'active' : ''" v-for="(item, index) of brandlist" :key = "index" @click="getlist(item, index)">
  <!-- <img :src="item.barndimg" alt=""> -->
  {{ item.brand }}
</li>
data () {
  return {
    kindlist: [],
    brandlist: [],
    prolist: [],
    kindindex: 0,
    brandindex: 0 // ++++++++++++++++++++++++++++
  }
},
methods: {
  getBrand (item, index) {
    this.kindindex = index
    this.brandindex = 0 // 点击左侧 永远选中的是右侧的第一个
  },
  getlist (item, index) {
    this.brandindex = index
  }
}
  • 默认显示第一个分类的品牌
created () {
    let token = localStorage.getItem('token')
    let url = '/pro/type?type=type&token=' + token
    axios.get(url).then(res => {
      console.log(res.data)
      if (res.data.code === '10119') {
        this.$router.push('/login')
      } else {
        this.kindlist = res.data.data
        this.getBrand(this.kindlist[0], 0) // +++++++++++++++++
      }
    })
  },
  • 默认显示第一个品牌的列表
getBrand (item, index) {
  this.kindindex = index
  this.brandindex = 0
  let token = localStorage.getItem('token')
  let url = '/pro/category?token=' + token + '&type=' + item
  axios.get(url).then((res) => {
    console.log(res.data)
    if (res.data.code === '10119') {
      this.$router.push('/login')
    } else {
      this.brandlist = res.data.data
      this.getlist(this.brandlist[0], 0) // +++++++++++++++++++++
    }
  })
},

10 搜索功能

一般首先显示的搜索框不是真正的 input

views/search/index.vue + router/index.js

posted @ 2019-11-01 14:04  菜鸟小何  阅读(270)  评论(0编辑  收藏  举报