Nuxtjs结合Element-ui框架应用项目详细记录---退出登录/注册功能/验证码/错误拦截器/机票首页静态

364 阅读1分钟

一、退出登录

思路:

  1. 在头部组件user/PageHeader.vue找到点击退出时的事件
  2. 点击登录时传递空对象使数据为空,插件再更新本地储存为空
// 用户退出
    handleLogout() {
      this.$store.commit("user/userInfo", {});
    },

在这里插入图片描述

二、注册功能

2.1注册表单的静态布局

element组件:

  • Form 表单
  • Input 输入框
  • Button 按钮

文件: component/user/RegisterFrom

<template>
  <!-- el-form 中:model绑定的是整个表单的数据 -->
  <!-- ref:标识符 -->
  <!-- rules:校验数据 -->
  <!-- el-form-item:每个输入框 -->
  <el-form :model="form" ref="form" :rules="rules" class="form">
    <el-form-item class="form-item">
      <el-input placeholder="用户名手机"> </el-input>
    </el-form-item>

    <el-form-item class="form-item">
      <el-input placeholder="验证码">
        <template slot="append">
          <!-- handleSendCaptcha:点击发送验证码时调用事件 -->
          <el-button @click="handleSendCaptcha"> 发送验证码 </el-button>
        </template>
      </el-input>
    </el-form-item>

    <el-form-item class="form-item">
      <el-input placeholder="你的名字"> </el-input>
    </el-form-item>

    <el-form-item class="form-item">
      <el-input placeholder="密码" type="password"></el-input>
    </el-form-item>

    <el-form-item class="form-item">
      <el-input placeholder="确认密码" type="password"> </el-input>
    </el-form-item>
    <!-- handleRegSubmit:点击事件发送注册请求 -->
    <el-button class="submit" type="primary" @click="handleRegSubmit">
      注册
    </el-button>
  </el-form>
</template>

<script>
export default {
  data() {
    return {
      //表单数据
      form: {},
      // 表单规则
      rules: {},
    };
  },
  methods: {
    //发送验证码
    handleSendCaptcha() {
      console.log("我要验证码");
    },
    //注册请求
    handleRegSubmit() {
      console.log("我要注册");
    },
  },
};
</script>

<style scoped lang="less">
.form {
  padding: 25px;
}

.form-item {
  margin-bottom: 20px;
}

.form-text {
  font-size: 12px;
  color: #409eff;
  text-align: right;
  line-height: 1;
}

.submit {
  width: 100%;
  margin-top: 10px;
}
</style>

效果

在这里插入图片描述

2.2引入登录页

  • 这部分前面写了,这里码云没记录

引入

import RegisterForm from "@/components/user/RegisterForm";

注册

 components: {
    RegisterForm, //注册表单
  },

把模块代码注释打开

        <!-- 注册功能组件 -->
        <RegisterForm v-if="currentTab == 1" />

2.3绑定数据

  • 根据接口要求设置变量,再用v-model绑定到对应input
  • 这里接口并未要求checkPassWord参数,添加是为了校验密码
export default {
  data() {
    return {
      //表单数据
      form: {
        username: "", //登录用户名,手机
        nickname: "", //昵称
        captcha: "", //手机验证
        password: "", //密码
        checkPassWord: "", //确认密码
      },
     //其他代码
  },

  },
};
    <el-form-item class="form-item">
      <el-input placeholder="用户名手机" v-model="form.username"> </el-input>
    </el-form-item>

    <el-form-item class="form-item">
      <el-input placeholder="验证码" v-model="form.captcha">
        <template slot="append">
          <!-- handleSendCaptcha:点击发送验证码时调用事件 -->
          <el-button @click="handleSendCaptcha"> 发送验证码 </el-button>
        </template>
      </el-input>
    </el-form-item>

    <el-form-item class="form-item">
      <el-input placeholder="你的名字" v-model="form.nickname"> </el-input>
    </el-form-item>

    <el-form-item class="form-item">
      <el-input
        placeholder="密码"
        type="password"
        v-model="form.password"
      ></el-input>
    </el-form-item>

    <el-form-item class="form-item">
      <el-input
        placeholder="确认密码"
        type="password"
        v-model="form.checkPassWord"
      >
      </el-input>
    </el-form-item>

效果

在这里插入图片描述

2.4数据校验与自定义校验函数

export default {
  data() {
    //由于是在data内完成校验,所以不写在methods也可以
    const validateCheckPass = (rule, value, callback) => {
      // rule:当前标准
      //value:当前输入框的值
      // callback:Element传入的固定回调函数,校验完成就需要执行
      // 1,如果校验合法直接执行,没有参数
      // 2.如果不合法,创建一个错误对象参数new Error
      if (!value) {
        callback(new Error("请输入必填信息"));
      } else if (value != this.form.password) {
        callback(new Error("两次密码必须一致"));
      } else {
        callback();
      }
    };
    return {
      //表单数据
      form: {
        username: "", //登录用户名,手机
        nickname: "", //昵称
        captcha: "", //手机验证
        password: "", //密码
        checkPassWord: "", //确认密码
      },
      // 表单规则
      rules: {
        username: [
          {
            required: true,
            message: "请输入用户名",
            trigger: "blur",
          },
        ],
        nickname: [
          {
            required: true,
            message: "请输入昵称",
            trigger: "blur",
          },
        ],
        captcha: [
          {
            required: true,
            message: "请输入验证码",
            trigger: "blur",
          },
        ],
        password: [
          {
            required: true,
            message: "请输入密码",
            trigger: "blur",
          },
        ],
        checkPassWord: [
          {
            trigger: "blur",
            //这里的校验并非默认自带的校验方式
            //判断标准和提示语都需要自定义
            // 自定义校验函数validator
            validator: validateCheckPass,
          },
        ],
      },
    };
  },
  }

2.5发送验证码

  • 发送验证码的逻辑是前端发送验证码请求到后台,后台生成验证码通过中间的电信公司发送到客户手上.发送注册请求时带上验证码给后台校验.
  • 而前端的话只需要发送axios请求即可

在这里插入图片描述

2.5.1接口

  • user/user文件
//验证码请求
export const sendCaptcha = (tel) => {
    return axios({
        url: '/captchas',
        method: 'post',
        data: {
            tel
        }
    })
}

2.5.2发送验证码请求

  • user/RegisterForm文件
//发送验证码
    handleSendCaptcha() {
      const regexp = /^1[3456789]\d{9}$/;
      if (!regexp.test(this.form.username)) {
        return this.$message.error("请输入合法手机号");
      }
      sendCaptcha(this.form.username).then((res) => {
        console.log(res.data);
      });
    },

2.5.3效果

在这里插入图片描述

2.6发送注册

  • 这里注册接口参数只有四个,并没有确认密码.所以在发送的时候需要把确认密码那项数据筛选下来.

2.6.1接口

  • user/user文件
//注册接口
export const register = (data) => {
    return axios({
        url: '/accounts/register',
        method: 'post',
        data
    })
}

2.6.2发送注册请求

  • user/RegisterForm文件
  • 使用...剩余运算法分离确认密码的数据
  • 发送前先进行全表单校验
   //注册请求
    handleRegSubmit() {
    //发送前调用element自带的验证validate,如果校验通过才发送
      this.$refs.form.validate().then((valid) => {
        if (valid) {
          const { checkPassWord, ...data } = this.form;
          register(data).then((res) => {
            console.log(res.data);
          });
        }
      });
    },

2.6.3效果

在这里插入图片描述

三、错误拦截器

3.1效果

在这里插入图片描述

四、机票首页

pages/air/index.vue element组件:

  • Layout 布局

4.1静态布局

<template>
  <section class="container">
    <h2 class="air-title"><span class="iconfont iconfeiji"></span>
      <i>国内机票</i>
    </h2>

    <!-- 搜索广告栏 -->
    <el-row type="flex" justify="space-between">
        <!-- 搜索表单 -->
        <div>搜索</div>

        <!-- banner广告 -->
        <div class="sale-banner">
            <img src="http://157.122.54.189:9093/images/pic_sale.jpeg">
        </div>
    </el-row>

    <!-- 广告 -->
    <el-row type="flex" class="statement">
      <el-col :span="8">
        <i class="iconfont iconweibiaoti-_huabanfuben" style="color:#409EFF;"></i>
        <span>100%航协认证</span>
      </el-col>
      <el-col :span="8">
        <i class="iconfont iconbaozheng" style="color:green;"></i>
        <span>出行保证</span>
      </el-col>
      <el-col :span="8">
        <i class="iconfont icondianhua" style="color:#409EFF;"></i>
        <span>7x24小时服务</span>
      </el-col>
    </el-row>

    <h2 class="air-sale-title">
      <span class="iconfont icontejiajipiao"></span>
      <i>特价机票</i>
    </h2>

    <!-- 特价机票 -->
    <div class="air-sale">
        
    </div>
  </section>
</template>

<script>
export default {

}
</script>

<style scoped lang="less">
.air-sale{
  border: 1px #ddd solid;
  padding:20px;
  margin-bottom:50px;

  .air-sale-pic{
    > div{
      width:225px;
      height:140px;
      position: relative;
      overflow: hidden;

      img{
        width:100%;
      }

      .layer-bar{
        position:absolute;
        bottom:0;
        left:0;
        background: rgba(0,0,0,0.5);
        color:#fff;
        height:30px;
        line-height: 30px;
        width:100%;
        box-sizing: border-box;
        padding: 0 15px;
        font-size: 14px;

        span:last-child{
          font-size:18px;
        }
      }
    }
  }
}

.air-sale-group{
  margin-top:20px;
  padding-top:8px;
  border-right:1px #eee solid;

  &:last-child{
    border-right:none;
  }

  .air-sale-row{
    font-size:12px;
    color:#666;
    margin-bottom:8px;

    .air-sale-price{
      color:orange;
      font-size: 20px;
    }
  }
}

.container{
  width:1000px;
  margin:0 auto;
}

.air-title{
  margin:15px 0;
  font-size:20px;
  font-weight: normal;
  color:orange;

  span{
    font-size:20px;
  }
}

.statement{
  margin:15px 0;
  border:1px #ddd solid;
  background:#f5f5f5;
  height: 58px;
  padding:10px 0;
  box-sizing:border-box;

  > div{
    text-align: center;
    line-height: 38px;
    border-right:1px #ddd solid;

    &:last-child{
      border-right: none;
    }

    *{
      vertical-align: middle;
    }

    i{
      font-size:30px;
    }
  }
}

.air-sale-title{
  margin:15px 0;
  font-size:20px;
  font-weight: normal;
  color:#409EFF;

  span{
    font-size:20px;
  }
}
</style>

4.2静态效果

在这里插入图片描述

4.3封装表单搜索组件(静态)

  • 新建机票搜索表单组件components/air/searchForm.vue
  • element组件: autocomplete远程搜索自动补全页面(在input里面) form表单 TimePicker 时间选择器 -点击单双程切换tab,用户输入字段时匹配数据给出关键字,点击关键字时做出处理
<template>
  <div class="search-form">
    <!-- 头部tab切换 -->
    <el-row type="flex" class="search-tab">
      <!-- active:循环点击的索引赋值class -->
      <!-- handleSearchTab:点击切换单双程样式事件 -->
      <span
        v-for="(item, index) in tabs"
        :key="index"
        @click="handleSearchTab(index)"
        :class="{ active: index === currentTab }"
      >
        <i :class="item.icon"></i>{{ item.name }}
      </span>
    </el-row>

    <el-form class="search-form-content" ref="form" label-width="80px">
      <el-form-item label="出发城市">
        <!-- fetch-suggestions 返回输入建议的方法 -->
        <!-- select 点击选中建议项时触发 -->
        <!-- fetch-suggestions:返回输入建议的方法属性,
        返回两个参数,第一个是当前用户输入的字段 ,第二个是显示建议的函数-->
        <!-- select:建议被选择之后的回调事件 -->
        <el-autocomplete
          :fetch-suggestions="queryDepartSearch"
          placeholder="请搜索出发城市"
          @select="handleDepartSelect"
          class="el-autocomplete"
        ></el-autocomplete>
      </el-form-item>
      <el-form-item label="到达城市">
        <el-autocomplete
          :fetch-suggestions="queryDestSearch"
          placeholder="请搜索到达城市"
          @select="handleDestSelect"
          class="el-autocomplete"
        ></el-autocomplete>
      </el-form-item>
      <el-form-item label="出发时间">
        <!-- change 用户确认选择日期时触发 -->
        <el-date-picker
          type="date"
          placeholder="请选择日期"
          style="width: 100%"
          @change="handleDate"
        >
        </el-date-picker>
      </el-form-item>
      <el-form-item label="">
        <el-button
          style="width: 100%"
          type="primary"
          icon="el-icon-search"
          @click="handleSubmit"
        >
          搜索
        </el-button>
      </el-form-item>
      <div class="reverse">
        <span @click="handleReverse"></span>
      </div>
    </el-form>
  </div>
</template>

<script>
export default {
  data() {
    return {
      tabs: [
        { icon: "iconfont icondancheng", name: "单程" },
        { icon: "iconfont iconshuangxiang", name: "往返" },
      ],
      currentTab: 0,
    };
  },
  methods: {
    // 切换tab
    handleSearchTab(index) {
      this.currentTab = index;
    },
    // 搜索
    handleSubmit() {},
    //单双程切换
    handleReverse() {},
    // 出发城市建议被选择之后的回调事件
    handleDepartSelect(item) {
      //这里是用户选择了其中一个建议选项之后出发的事件
      //可以接收到用户选择选项的对象本身
      //不单单是渲染项,而是整个对象,可以进行后续操作
      console.log(item);
    },
    //到达城市的建议回调
    handleDestSelect(item) {},
    // 出发城市的返回输入建议的方法属性
    queryDepartSearch(string, callback) {
      //这个是显示建议的函数
      // 第一个参数是当前输入框的值
      //第二个是用来显示建议列表的回调函数
      //在调用这个函数的时候往里面输入一个建议数组即可
      //数组中每个元素都是一个建议对象
      this.$axios({
        url: "/airs/city",
        params: {
          name: string,
        },
      }).then((res) => {
        console.log(res.data.data);
        const list = res.data.data.map((city) => {
          return {
            ...city, //展开数据
            value: city.name, //框架需要value值
          };
        });
        callback(list);
      });
    },
    // 到达城市的建议函数
    queryDestSearch(string, callback) {},
    // 日期选择
    handleDate() {},
  },
};
</script>

<style scoped lang="less">
.search-form {
  border: 1px #ddd solid;
  border-top: none;
  width: 360px;
  height: 350px;
  box-sizing: border-box;
}

.search-tab {
  span {
    display: block;
    flex: 1;
    text-align: center;
    height: 48px;
    line-height: 42px;
    box-sizing: border-box;
    border-top: 3px #eee solid;
    background: #eee;
  }

  .active {
    border-top-color: orange;
    background: #fff;
  }

  i {
    margin-right: 5px;
    font-size: 18px;

    &:first-child {
      font-size: 16px;
    }
  }
}

.search-form-content {
  padding: 15px 50px 15px 15px;
  position: relative;

  .el-autocomplete {
    width: 100%;
  }
}

.reverse {
  position: absolute;
  top: 35px;
  right: 15px;

  &:after,
  &:before {
    display: block;
    content: "";
    position: absolute;
    left: -35px;
    width: 25px;
    height: 1px;
    background: #ccc;
  }

  &:after {
    top: 0;
  }

  &:before {
    top: 60px;
  }

  span {
    display: block;
    position: absolute;
    top: 20px;
    right: 0;
    font-size: 12px;
    background: #999;
    color: #fff;
    width: 20px;
    height: 20px;
    line-height: 18px;
    text-align: center;
    border-radius: 2px;
    cursor: pointer;

    &:after,
    &:before {
      display: block;
      content: "";
      position: absolute;
      left: 10px;
      width: 1px;
      height: 20px;
      background: #ccc;
    }

    &:after {
      top: -20px;
    }

    &:before {
      top: 20px;
    }
  }
}
</style>

4.4引入pages/air/index注册使用

//引入
import searchForm from "../../components/air/searchForm";
//注册
  components: {
    searchForm,
  },

使用

 <!-- 搜索表单 -->
      <div>
        <searchForm />
      </div>