04 硅谷外卖

一、异步数据

1、封装ajax:

  • 要学会promise+axios封装ajax请求的函数
  • 要能封装每个接口对应的请求函数(根据接口定义ajax请求函数)
  • 解决ajax的跨域问题:配置代理,对代理的理解。
  • 配置--config--index.js
proxyTable: {
  '/api': { // 匹配所有以 '/api'开头的请求路径
    target: 'http://localhost:3000', // 代理目标的基础路径
    changeOrigin: true, // 支持跨域
    pathRewrite: {// 重写路径: 去掉路径中开头的'/api'
      '^/api': ''
    }
  }
}

2、vuex编码:

  1. 创建所有相关的模块: store/index|state|mutations|actions|getters|mutations-types
  2. 设计state:也就是从后台获取的数据存储在state中
    • 出现问题:对于暴露导入的理解
const state = {}
export default state
import state from './xxx.js'
//暴露时没有加{},在import时也不需要加{}
const state = {}
export default {state}
//export default {} 导出的是对象,那么引入的时候就要 import {state} from './xxx.js'
  1. 实现actions,此文件是用来异步获取数据的
    • 定义异步action: async/await
    • 流程: 发送ajax获取数据,commit提交给mutation
  2. 实现mutation:给状态赋值,也就是更新state数据
  3. 实现index:创建store对象(固定的模式)
  4. main.js: 配置store

3、组件异步显示数据,解决swiper动态加载数据问题

  1. 异步获取数据,解决swiper动态加载数据问题
    参考网址: 动态数据加载时,swiper的应用和处理 - 简书
    • 在mounted()通过$store.dispatch('actionName')来异步获取后台数据到state中
    • mapActions(['getAddress'])以数组形式映射模块中的方法,在mounted中调用也可以异步获取后台数据
import { mapActions } from "vuex";
methods: {
 //TODO: 方法2.以数组形式映射模块中的方法
 ...mapActions(['getAddress'])
},
mounted () {
 //TODO: 方法1.含有异步操作,例如向后台提交数据
 // this.$store.dispatch('getAddress')
 this.getAddress();
}
  1. 读取数据
    • mapState(['...'])读取state中数据到组件中
  2. 显示数据
    • 在模板中显示xxx数据

4、模板中显示数据的来源

  1. data:自身的数据(内部改变)
  2. props:外部传入的数据(外部改变)
  3. computed: 根据data/props/别的compute/state/getters(计算的数据)

5、异步显示轮播图

  1. 通过vuex获取foodCategorys数据(发请求,读取)
  2. 对获取的数据进行整合计算(一维变特定的二维数据)

  computed: {
    ...mapState(["categorys"]),
    /*
      根据categorys一维数组生成一个2维数组
      小数组中的元素个数最大是8
       */
    categorysArr() {
      //解构赋值
      const { categorys } = this;
      // 准备空的2维数组
      const arr = [];
      // 准备一个小数组(最大长度为8)
      let minArr = [];
      categorys.forEach(c => {
        // 如果当前小数组已经满了, 创建一个新的
        if (minArr.length === 8) {
          minArr = [];
        }
        // 如果minArr是空的, 将小数组保存到大数组中
        if (minArr.length === 0) {
          arr.push(minArr);
        }
        // 将当前分类保存到小数组中
        minArr.push(c);
      });
      return arr;
    }
  },
  1. 通过双循环对二维数组进行遍历显示,如果没有数据时,显示预加载图片
<nav class="msite_nav">
 <!-- 判断是否有数据 -->
 <div class="swiper-container" v-if="categorys.length">
   <div class="swiper-wrapper">
     <!-- 第一层遍历二维数组 -->
     <div class="swiper-slide" v-for="(categorys, index) in categorysArr" :key="index">
       <!-- 第二层遍历,对二维数组中的一维数组遍历 -->
       <a href="javascript:" class="link_to_food" v-for="(category, index) in categorys" :key="index">
         <div class="food_container">
           <img :src="baseImageUrl+category.image_url">
         </div>
         <span>{{category.title}}</span>
       </a>
     </div>

   </div>
   <!-- Add Pagination -->
   <div class="swiper-pagination"></div>
 </div>
 <!-- 没有数据时显示预加载svg -->
 <img src="./images/msite_back.svg" alt="back" v-else >
</nav>
  1. 使用Swiper显示轮播图,如何在界面更新之后创建Swiper对象?
    • 使用watch+$nextTick(),监视显示的数据,如果数据有值立即更新

开始是个空数组,数组请求之后加载轮播图

  watch: {
    categorys(value) {
      // categorys数组中有数据了, 在异步更新界面之前执行
      // 使用setTimeout可以实现效果, 但不是太好
      /*setTimeout(() => {
       //创建一个Swiper实例对象,实现轮播
        new Swiper(".swiper-container", {
          loop: true, // 循环模式选项,可以循环轮播
          // 如果需要分页器
          pagination: {
            el: ".swiper-pagination"
          }
        });
      }, 100);*/
       // 界面更新就立即创建Swiper对象
        this.$nextTick(() => {// TODO: 一旦完成界面更新, 立即调用(此条语句要写在数据更新之后)
          // 创建一个Swiper实例对象, 来实现轮播
          new Swiper('.swiper-container', {
            loop: true, // 可以循环轮播
            // 如果需要分页器
            pagination: {
              el: '.swiper-pagination',
            },
          }) 
        })
    }
  }

6  使用svg显示加载中提示页面:

1 msite_back.svg:

2  给shopList添加svg: 

整多个进行展示:

二、登录/注册:界面相关效果

2.1切换登录方式

  • 初始化一个boolean值(longinWay),true为短信登录,false为密码登录
  • 使用@click方法设置boolean值
  • 定义一个class类绑定此布尔值控制表单显示选择
    loginWay: true //true代表短信登陆, false代表密码

 两个div在一个form表单当中,最后只提交一个请求


          &.on {
            color: #02a774;
            font-weight: 700;
            border-bottom: 2px solid #02a774;
          }

2.2手机号合法检查,修改button的字体颜色

  • 通过计算属性验证输入的手机号是否正确,正确时可以点击发送获取验证码,right_phone类改变字的颜色

2.3 添加倒计时

点击获取 验证码, 变成倒计时

 

 什么时候启动计时: 当前没有计时(防止用户点击两次, 启动两个定时器)

 <section class="login_message">
  <input type="tel" maxlength="11" placeholder="手机号" v-model="phone">
  <!-- 计算属性验证输入的手机号是否正确,正确则能点击获取验证码,添加显示状态。 -->
  <!-- 表单里面的button默认行为是提交表单,要取消默认行为 -->
  <!-- 三目运算,模板字符串,如果时间大于0则显示当前秒数 -->
  <button
    :disabled="!rightPhone"
    class="get_verification"
    :class="{right_phone: rightPhone}"
    @click.prevent="getCode"
  >{{computeTime>0? `已发送(${computeTime}s)` :'获取验证码'}}</button>
</section>
data() {
  return {
    phone: "", // 手机号
    computeTime: 0, //计时时间
  }
},
computed: {
    rightPhone() {
      //使用计算属性测试输入的手机号是否正确,正确则显示类名
      return /^1\d{10}$/.test(this.phone);
    }
  },
methods: {
  //异步获取验证码
  getCode() {
    //如果当时没有计时
    if (!this.computeTime) {
      //启动倒计时
      this.computeTime = 30;
      //TODO: 间歇调用
      const intervalId = setInterval(() => {
        this.computeTime--;
        if (this.computeTime <= 0) {
          clearInterval(intervalId);
        }
      }, 1000);
    }
  }
}

2.4切换显示或隐藏密码

  • 通过两个输入框,一个按钮,改变密码的显示与隐藏状态

showPwd: false, //是否显示密码

 

    .login_verification {
            position: relative;
            margin-top: 16px;
            height: 48px;
            font-size: 14px;
            background: #fff;

            .switch_button {
              font-size: 12px;
              border: 1px solid #ddd;
              border-radius: 8px;
              transition: background-color 0.3s, border-color 0.3s;
              padding: 0 6px;
              width: 30px;
              height: 16px;
              line-height: 16px;
              color: #fff;
              position: absolute;
              top: 50%;
              right: 10px;
              transform: translateY(-50%);

              &.off {
                background: #fff;

                .switch_text {
                  float: right;
                  color: #ddd;
                }
              }

              &.on {
                background: #02a774;
              }

              >.switch_circle {
                position: absolute;
                top: -1px;
                left: -1px;
                width: 16px;
                height: 16px;
                border: 1px solid #ddd;
                border-radius: 50%;
                background: #fff;
                box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.1);
                transition: transform 0.3s;

                &.right {
                  transform: translateX(30px);
                }
              }
            }
<section class="login_verification">
  <!-- 切换密码显示隐藏状态,改变按钮选择状态,默认为隐藏 -->
  <input type="text" maxlength="8" placeholder="密码" v-if="showPwd" v-model="pwd">
  <input type="password" maxlength="8" placeholder="密码" v-if="!showPwd" v-model="pwd">
  <!-- 点击改变类的状态和圆圈的移动 -->
  <div class="switch_button" :class="showPwd?'on': 'off'" @click="showPwd = !showPwd">
    <div class="switch_circle" :class="{right:showPwd}"></div>
    <span class="switch_text">{{showPwd?'abc':'...'}}</span>
  </div>
</section>

2.5 前台验证提示

  • 学会如何使用模板组件
  • 调用--components-AlertTip-AlertTip.vue组件,使用提示框组件

<!-- 提示模板组件 -->
<template>
  <div class="alert_container">
    <section class="tip_text_container">
      <div class="tip_icon">
        <span></span>
        <span></span>
      </div>
      <p class="tip_text">{{alertText}}</p>
      <div class="confrim" @click="closeTip">确认</div>
    </section>
  </div>
</template>

<script>
  export default {
    props: {
      alertText: String  //传入的参数
    },

    methods: {
      closeTip() {
        // 分发自定义事件(事件名: closeTip)
        this.$emit('closeTip')
      }
    }
  }
</script>

使用:

 具体代码:

<form @submit.prevent="login">
</form>
<!-- 调用组件,@closeTip自定义事件 -->
<AlertTip :alertText="alertText" v-show="alertShow" @closeTip="closeTip"/>
import AlertTip from "../../components/AlertTip/AlertTip.vue";
data() {
  return {
      alertText: "", //提示文本
      alertShow: false //是否显示警告框
  }
}
methods: {
  //展示提示框
  showAlert(alertText) {
    this.alertShow = true;
    this.alertText = alertText;
  },
 //关闭警告框
  closeTip() {
    this.alertShow = false;
    this.alertText = '';
  },
  //异步登录
  login() {
    //前台表单验证
    if (this.loginWay) {
      //短信登录
      const { phone, code } = this;
      if (!this.rightPhone) {
        //手机号不正确
        this.showAlert("手机号不正确");
      } else if (!/^\d{6}$/.test(code)) {
        alert(this.code)
        //验证码必须是6位数字
        this.showAlert("验证码必须是6位数字");
      }
    } else {
      //密码登录
      const { name, pwd, captcha } = this;
      if (!this.name) {
        //用户名必须指定
        this.showAlert("用户名必须指定");
      } else if (!this.pwd) {
        //密码必须指定
        this.showAlert("密码必须指定");
      } else if (!this.captcha) {
        //图形验证码必须指定
        this.showAlert("图形验证码必须指定");
      }
    }
  }  
}

2.6 一次性图形验证码

是一个svg,里边有一些干扰线

要每一次的图片地址改变,才能刷新, 给后边加一个时间戳

这不是ajax请求,不存在跨域问题

 <section class="login_message">
                <input type="text" maxlength="11" placeholder="验证码" v-model="captcha">
                <!-- <img class="get_verification" src="./images/captcha.svg" alt="captcha"> -->
                <img class="get_verification" src="http://localhost:3000/captcha" alt="captcha"
                @click="getCaptcha" ref="captcha">
              </section>
//获取一个新的验证码
    getCaptcha () {
        // 每次指定的src要不一样
        this.$refs.captcha.src = 'http://localhost:3000/captcha?time='+Date.now()
      }

2.7 发送短信验证码

 

 

 后台根据api写接口, 前端只需要调用接口,  后续做操作

我们需要失败之后给出提示,并清空倒计时

 

三 完成登录注册功能

  1. 2种登录方式
    • 手机号/验证码登录
    • 用户名/密码/图片验证码登录
  2. 登录的基本流程
    • 表单前台验证,如果不通过,提示
    • 发送ajax请求,得到返回的结果
  3. 根据结果的标识(code)来判断登录请求是否成功
    • 1: 不成功,显示提示
    • 0: 成功,保存用户信息,跳转到个人中心路由

async login() {
 let result
 //前台表单验证
 if (this.loginWay) {
   // 发送ajax请求,短信登录
   result = await reqSmsLogin(phone,code)

 } else {
   // 发送ajax请求,密码登录
   result = await reqPwdLogin({name,pwd,captcha})
 }
 //根据结果数据处理
   if(result.code === 0) {
     const user = result.data
     //将user保存到vuex的state中
     this.$store.dispatch('recordUser',user)
     //去个人中心界面
     this.$router.replace('/profile')
   } else {
     //显示新的图形验证码
     this.getCaptcha()
     //显示警告提示
     const msg = result.msg
     this.showAlert(msg)
   }
}








//action.js
  // TODO: 同步记录用户信息
  recordUser ({commit},userInfo) {
    commit(RECEIVE_USER_INFO,{userInfo})
  }
//mutations.js
  [RECEIVE_USER_INFO](state,{userInfo}) {
      state.userInfo = userInfo
  },

 登录之后调回个人中心页面,要更新数据

 

点击头像,现在应该进个人中心,而不是登录页

 

 头部设置:

 

 

四 自动登录,退出登录

 

  1. 通过会话获取后台用户信息,后台处理session保持登录状态,刷新页面时登录存在
// 根据会话获取用户信息
export const reqUserInfo = () => ajax(BASE_URL + '/userinfo')
  // 异步获取用户信息
  async getUserInfo({commit}) {
    const result = await reqUserInfo()
    if(result.code === 0) {
      const userInfo = result.data
      commit(RECEIVE_USER_INFO,{userInfo})
    }
  },

 

 

  1. 点击退出登录,返回退出登录状态给后台,删除前台用户信息
logout() {
   Dialog.confirm({
     title: "提示",
     message: "确认退出吗"
   })
   .then(() => {
     //请求退出
     this.$store.dispatch("logout");
     Toast('退出成功');
   })
   .catch(() => {
     console.log("点击了取消");
   });
}
//actions
  // 异步登出
  async logout ({commit}) {
    const result = await reqLogout()
    if(result.code === 0) {
      commit(RESET_USER_INFO)
    }
  }


 


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值