• 项目--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

  • 相关阅读:
    《《《Postman 的官网下转
    《《《Springboot 配置 application.yml 连接MySQL数据库
    《《《MyBatis-Plus完成可用项目(通过测试可以连接数据库并返回数据(可以当作开发模板使用))
    《《《spring boot配置文件application.yml出现的异常信息:(java.lang.IllegalStateException: Failed to load property source from location 'classpath:/application.yml')
    《《《转载 Intellij IDEA 导入Maven项目
    start.spring.io访问不了,导致springboot项目创建不起来(Initialization failed for 'https://start.spring.io' Please check URL, network and proxy settings. Error mes...)
    《《《MyBatis-Plus入门 视频学习笔记
    在pom.xml中查看引入关系图
    《《《如何建立局域网共享文件夹(怎样通过IP地址共享文件夹(同一个局域网下))
    JS跨域问题及其解决办法
  • 原文地址:https://www.cnblogs.com/hy96/p/11776636.html
Copyright © 2020-2023  润新知